diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Bcfg2/Bcfg2Py3k.py (renamed from src/lib/Bcfg2Py3k.py) | 11 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Frame.py (renamed from src/lib/Client/Frame.py) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/APK.py (renamed from src/lib/Client/Tools/APK.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/APT.py (renamed from src/lib/Client/Tools/APT.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Action.py (renamed from src/lib/Client/Tools/Action.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Blast.py (renamed from src/lib/Client/Tools/Blast.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Chkconfig.py (renamed from src/lib/Client/Tools/Chkconfig.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/DebInit.py (renamed from src/lib/Client/Tools/DebInit.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Encap.py (renamed from src/lib/Client/Tools/Encap.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/FreeBSDInit.py (renamed from src/lib/Client/Tools/FreeBSDInit.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py (renamed from src/lib/Client/Tools/FreeBSDPackage.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/IPS.py (renamed from src/lib/Client/Tools/IPS.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/MacPorts.py (renamed from src/lib/Client/Tools/MacPorts.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/POSIX.py (renamed from src/lib/Client/Tools/POSIX.py) | 23 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Pacman.py (renamed from src/lib/Client/Tools/Pacman.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Portage.py | 125 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/RPMng.py (renamed from src/lib/Client/Tools/RPMng.py) | 10 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/RcUpdate.py (renamed from src/lib/Client/Tools/RcUpdate.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/SMF.py (renamed from src/lib/Client/Tools/SMF.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/SYSV.py (renamed from src/lib/Client/Tools/SYSV.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Systemd.py (renamed from src/lib/Client/Tools/Systemd.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/Upstart.py (renamed from src/lib/Client/Tools/Upstart.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/VCS.py (renamed from src/lib/Client/Tools/VCS.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/YUM24.py (renamed from src/lib/Client/Tools/YUM24.py) | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/YUMng.py (renamed from src/lib/Client/Tools/YUMng.py) | 10 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/__init__.py (renamed from src/lib/Client/Tools/__init__.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/launchd.py (renamed from src/lib/Client/Tools/launchd.py) | 1 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Client/Tools/rpmtools.py (renamed from src/lib/Client/Tools/rpmtools.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/XML.py (renamed from src/lib/Client/XML.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/__init__.py (renamed from src/lib/Client/__init__.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Component.py (renamed from src/lib/Component.py) | 6 | ||||
-rw-r--r-- | src/lib/Bcfg2/Logger.py (renamed from src/lib/Logger.py) | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Options.py (renamed from src/lib/Options.py) | 129 | ||||
-rw-r--r-- | src/lib/Bcfg2/Proxy.py (renamed from src/lib/Proxy.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/SSLServer.py (renamed from src/lib/SSLServer.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Backup.py (renamed from src/lib/Server/Admin/Backup.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Bundle.py (renamed from src/lib/Server/Admin/Bundle.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Client.py (renamed from src/lib/Server/Admin/Client.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Compare.py (renamed from src/lib/Server/Admin/Compare.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Group.py (renamed from src/lib/Server/Admin/Group.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Init.py (renamed from src/lib/Server/Admin/Init.py) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Minestruct.py (renamed from src/lib/Server/Admin/Minestruct.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Perf.py (renamed from src/lib/Server/Admin/Perf.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Pull.py (renamed from src/lib/Server/Admin/Pull.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Query.py (renamed from src/lib/Server/Admin/Query.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Reports.py (renamed from src/lib/Server/Admin/Reports.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Snapshots.py (renamed from src/lib/Server/Admin/Snapshots.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Tidy.py (renamed from src/lib/Server/Admin/Tidy.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Viz.py (renamed from src/lib/Server/Admin/Viz.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/Xcmd.py (renamed from src/lib/Server/Admin/Xcmd.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Admin/__init__.py (renamed from src/lib/Server/Admin/__init__.py) | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Core.py (renamed from src/lib/Server/Core.py) | 26 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/FileMonitor.py (renamed from src/lib/Server/FileMonitor.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/.gitignore (renamed from src/lib/Server/Hostbase/.gitignore) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/__init__.py (renamed from src/lib/Server/Hostbase/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/backends.py (renamed from src/lib/Server/Hostbase/backends.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/__init__.py (renamed from src/lib/Server/Hostbase/hostbase/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/admin.py (renamed from src/lib/Server/Hostbase/hostbase/admin.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/models.py (renamed from src/lib/Server/Hostbase/hostbase/models.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/sql/zone.sql (renamed from src/lib/Server/Hostbase/hostbase/sql/zone.sql) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/urls.py (renamed from src/lib/Server/Hostbase/hostbase/urls.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/views.py (renamed from src/lib/Server/Hostbase/hostbase/views.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/base.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/base.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/confirm.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/confirm.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/copy.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/copy.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dns.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/dns.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dnsedit.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/dnsedit.html) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/edit.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/edit.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/errors.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/errors.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/host.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/host.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/index.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/index.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/login.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/login.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/logout.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.tmpl (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/logout.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logviewer.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/logviewer.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/navbar.tmpl (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/navbar.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/new.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/new.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/remove.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/remove.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/results.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/results.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/search.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/search.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneedit.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/zoneedit.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zonenew.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/zonenew.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zones.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/zones.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneview.html (renamed from src/lib/Server/Hostbase/hostbase/webtemplates/zoneview.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/ldapauth.py (renamed from src/lib/Server/Hostbase/ldapauth.py) | 0 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Hostbase/manage.py (renamed from src/lib/Server/Hostbase/manage.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/media/base.css (renamed from src/lib/Server/Hostbase/media/base.css) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/media/boxypastel.css (renamed from src/lib/Server/Hostbase/media/boxypastel.css) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/media/global.css (renamed from src/lib/Server/Hostbase/media/global.css) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/media/layout.css (renamed from src/lib/Server/Hostbase/media/layout.css) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/nisauth.py (renamed from src/lib/Server/Hostbase/nisauth.py) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/regex.py (renamed from src/lib/Server/Hostbase/regex.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/settings.py (renamed from src/lib/Server/Hostbase/settings.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/batchadd.tmpl (renamed from src/lib/Server/Hostbase/templates/batchadd.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.conf.head (renamed from src/lib/Server/Hostbase/templates/dhcpd.conf.head) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.tmpl (renamed from src/lib/Server/Hostbase/templates/dhcpd.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/hosts.tmpl (renamed from src/lib/Server/Hostbase/templates/hosts.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/hostsappend.tmpl (renamed from src/lib/Server/Hostbase/templates/hostsappend.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/named.tmpl (renamed from src/lib/Server/Hostbase/templates/named.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/namedviews.tmpl (renamed from src/lib/Server/Hostbase/templates/namedviews.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/reverseappend.tmpl (renamed from src/lib/Server/Hostbase/templates/reverseappend.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/reversesoa.tmpl (renamed from src/lib/Server/Hostbase/templates/reversesoa.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/templates/zone.tmpl (renamed from src/lib/Server/Hostbase/templates/zone.tmpl) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/test/harness.py (renamed from src/lib/Server/Hostbase/test/harness.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/test/test_environ_settings.py (renamed from src/lib/Server/Hostbase/test/test_environ_settings.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/test/test_ldapauth.py (renamed from src/lib/Server/Hostbase/test/test_ldapauth.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/test/test_settings.py (renamed from src/lib/Server/Hostbase/test/test_settings.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Hostbase/urls.py (renamed from src/lib/Server/Hostbase/urls.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Bundles.py (renamed from src/lib/Server/Lint/Bundles.py) | 21 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Comments.py (renamed from src/lib/Server/Lint/Comments.py) | 47 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Duplicates.py (renamed from src/lib/Server/Lint/Duplicates.py) | 10 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Lint/Genshi.py (renamed from src/lib/Server/Lint/Genshi.py) | 12 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/GroupPatterns.py (renamed from src/lib/Server/Lint/GroupPatterns.py) | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/InfoXML.py (renamed from src/lib/Server/Lint/InfoXML.py) | 21 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/MergeFiles.py (renamed from src/lib/Server/Lint/MergeFiles.py) | 16 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Pkgmgr.py (renamed from src/lib/Server/Lint/Pkgmgr.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/RequiredAttrs.py (renamed from src/lib/Server/Lint/RequiredAttrs.py) | 9 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/TemplateHelper.py | 64 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Validate.py (renamed from src/lib/Server/Lint/Validate.py) | 16 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/__init__.py (renamed from src/lib/Server/Lint/__init__.py) | 52 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugin.py (renamed from src/lib/Server/Plugin.py) | 166 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Account.py (renamed from src/lib/Server/Plugins/Account.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/BB.py (renamed from src/lib/Server/Plugins/BB.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Base.py (renamed from src/lib/Server/Plugins/Base.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Bundler.py (renamed from src/lib/Server/Plugins/Bundler.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Bzr.py (renamed from src/lib/Server/Plugins/Bzr.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgCatFilter.py | 20 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py | 33 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgDiffFilter.py | 27 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgExternalCommandVerifier.py | 33 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py | 64 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py | 24 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py | 28 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgPlaintextGenerator.py | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py | 398 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cvs.py (renamed from src/lib/Server/Plugins/Cvs.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/DBStats.py (renamed from src/lib/Server/Plugins/DBStats.py) | 3 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Darcs.py (renamed from src/lib/Server/Plugins/Darcs.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Decisions.py (renamed from src/lib/Server/Plugins/Decisions.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Defaults.py (renamed from src/lib/Server/Plugins/Defaults.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Deps.py (renamed from src/lib/Server/Plugins/Deps.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Editor.py (renamed from src/lib/Server/Plugins/Editor.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/FileProbes.py (renamed from src/lib/Server/Plugins/FileProbes.py) | 12 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Fossil.py (renamed from src/lib/Server/Plugins/Fossil.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Git.py (renamed from src/lib/Server/Plugins/Git.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/GroupPatterns.py (renamed from src/lib/Server/Plugins/GroupPatterns.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Guppy.py (renamed from src/lib/Server/Plugins/Guppy.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Hg.py (renamed from src/lib/Server/Plugins/Hg.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Hostbase.py (renamed from src/lib/Server/Plugins/Hostbase.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Ldap.py (renamed from src/lib/Server/Plugins/Ldap.py) | 53 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Metadata.py (renamed from src/lib/Server/Plugins/Metadata.py) | 628 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/NagiosGen.py (renamed from src/lib/Server/Plugins/NagiosGen.py) | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Ohai.py (renamed from src/lib/Server/Plugins/Ohai.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Apt.py (renamed from src/lib/Server/Plugins/Packages/Apt.py) | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Collection.py (renamed from src/lib/Server/Plugins/Packages/Collection.py) | 67 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Pac.py (renamed from src/lib/Server/Plugins/Packages/Pac.py) | 9 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py (renamed from src/lib/Server/Plugins/Packages/PackagesSources.py) | 24 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Source.py (renamed from src/lib/Server/Plugins/Packages/Source.py) | 14 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/Yum.py (renamed from src/lib/Server/Plugins/Packages/Yum.py) | 72 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Packages/__init__.py (renamed from src/lib/Server/Plugins/Packages/__init__.py) | 77 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Pkgmgr.py (renamed from src/lib/Server/Plugins/Pkgmgr.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Probes.py (renamed from src/lib/Server/Plugins/Probes.py) | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Properties.py (renamed from src/lib/Server/Plugins/Properties.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Rules.py (renamed from src/lib/Server/Plugins/Rules.py) | 10 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/SGenshi.py (renamed from src/lib/Server/Plugins/SGenshi.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/SSHbase.py (renamed from src/lib/Server/Plugins/SSHbase.py) | 29 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/SSLCA.py (renamed from src/lib/Server/Plugins/SSLCA.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Snapshots.py (renamed from src/lib/Server/Plugins/Snapshots.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Statistics.py (renamed from src/lib/Server/Plugins/Statistics.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Svcmgr.py (renamed from src/lib/Server/Plugins/Svcmgr.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Svn.py (renamed from src/lib/Server/Plugins/Svn.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Svn2.py (renamed from src/lib/Server/Plugins/Svn2.py) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/TCheetah.py (renamed from src/lib/Server/Plugins/TCheetah.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/TGenshi.py (renamed from src/lib/Server/Plugins/TGenshi.py) | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/TemplateHelper.py | 83 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Trigger.py (renamed from src/lib/Server/Plugins/Trigger.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/__init__.py (renamed from src/lib/Server/Plugins/__init__.py) | 3 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/__init__.py (renamed from src/lib/Server/Reports/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/backends.py (renamed from src/lib/Server/Reports/backends.py) | 0 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Reports/importscript.py (renamed from src/lib/Server/Reports/importscript.py) | 11 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Reports/manage.py (renamed from src/lib/Server/Reports/manage.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/nisauth.py (renamed from src/lib/Server/Reports/nisauth.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/__init__.py (renamed from src/lib/Server/Reports/reports/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/fixtures/initial_version.xml (renamed from src/lib/Server/Reports/reports/fixtures/initial_version.xml) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/models.py (renamed from src/lib/Server/Reports/reports/models.py) | 15 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/sql/client.sql (renamed from src/lib/Server/Reports/reports/sql/client.sql) | 4 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/404.html (renamed from src/lib/Server/Reports/reports/templates/404.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/base-timeview.html (renamed from src/lib/Server/Reports/reports/templates/base-timeview.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/base.html (renamed from src/lib/Server/Reports/reports/templates/base.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/clients/detail.html (renamed from src/lib/Server/Reports/reports/templates/clients/detail.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/clients/detailed-list.html (renamed from src/lib/Server/Reports/reports/templates/clients/detailed-list.html) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/clients/history.html (renamed from src/lib/Server/Reports/reports/templates/clients/history.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html | 34 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/clients/manage.html (renamed from src/lib/Server/Reports/reports/templates/clients/manage.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/config_items/item.html (renamed from src/lib/Server/Reports/reports/templates/config_items/item.html) | 15 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/config_items/listing.html (renamed from src/lib/Server/Reports/reports/templates/config_items/listing.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/displays/summary.html (renamed from src/lib/Server/Reports/reports/templates/displays/summary.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/displays/timing.html (renamed from src/lib/Server/Reports/reports/templates/displays/timing.html) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/widgets/filter_bar.html (renamed from src/lib/Server/Reports/reports/templates/widgets/filter_bar.html) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/widgets/interaction_list.inc (renamed from src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templates/widgets/page_bar.html (renamed from src/lib/Server/Reports/reports/templates/widgets/page_bar.html) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templatetags/__init__.py (renamed from src/lib/Server/Reports/reports/templatetags/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templatetags/bcfg2_tags.py (renamed from src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py) | 207 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templatetags/split.py | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/templatetags/syntax_coloring.py (renamed from src/lib/Server/Reports/reports/templatetags/syntax_coloring.py) | 11 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/urls.py (renamed from src/lib/Server/Reports/reports/urls.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/reports/views.py (renamed from src/lib/Server/Reports/reports/views.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/settings.py (renamed from src/lib/Server/Reports/settings.py) | 12 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/updatefix.py (renamed from src/lib/Server/Reports/updatefix.py) | 113 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Reports/urls.py (renamed from src/lib/Server/Reports/urls.py) | 0 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Reports/utils.py (renamed from src/lib/Server/Reports/utils.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Snapshots/__init__.py (renamed from src/lib/Server/Snapshots/__init__.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Snapshots/model.py (renamed from src/lib/Server/Snapshots/model.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/__init__.py (renamed from src/lib/Server/__init__.py) | 2 | ||||
-rw-r--r-- | src/lib/Bcfg2/Statistics.py (renamed from src/lib/Statistics.py) | 0 | ||||
-rw-r--r-- | src/lib/Bcfg2/__init__.py (renamed from src/lib/__init__.py) | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2Py3Incompat.py | 2 | ||||
-rw-r--r-- | src/lib/Client/Tools/Portage.py | 72 | ||||
-rw-r--r-- | src/lib/Server/Lint/Deltas.py | 19 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Cfg.py | 295 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Packages/PackagesConfig.py | 15 | ||||
-rw-r--r-- | src/lib/Server/Reports/reports/templates/clients/index.html | 34 |
224 files changed, 2107 insertions, 1434 deletions
diff --git a/src/lib/Bcfg2Py3k.py b/src/lib/Bcfg2/Bcfg2Py3k.py index ee05b7e41..6af8b3e5c 100644 --- a/src/lib/Bcfg2Py3k.py +++ b/src/lib/Bcfg2/Bcfg2Py3k.py @@ -75,17 +75,6 @@ def u_str(string, encoding=None): else: return unicode(string) -""" -In order to use the new syntax for printing to a file, we need to do -a conditional import because there is a syntax incompatibility between -the two versions of python. -""" -if sys.hexversion >= 0x03000000: - from Bcfg2.Bcfg2Py3Incompat import fprint -else: - def fprint(s, f): - print >> f, s - if sys.hexversion >= 0x03000000: from io import FileIO as file else: diff --git a/src/lib/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index d17f70f1b..9ad669ad6 100644 --- a/src/lib/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -2,7 +2,6 @@ Frame is the Client Framework that verifies and installs entries, and generates statistics. """ -__revision__ = '$Revision$' import logging import sys @@ -292,7 +291,7 @@ class Frame: if self.setup['interactive']: promptFilter(prompt, actions) self.DispatchInstallCalls(actions) - + # need to test to fail entries in whitelist if False in [self.states[a] for a in actions]: # then display bundles forced off with entries @@ -427,7 +426,6 @@ class Frame: stats = Bcfg2.Client.XML.SubElement(feedback, 'Statistics', total=str(len(self.states)), - client_version=__revision__, version='2.0', revision=self.config.get('revision', '-1')) good = len([key for key, val in list(self.states.items()) if val]) diff --git a/src/lib/Client/Tools/APK.py b/src/lib/Bcfg2/Client/Tools/APK.py index 6a6fd51b3..aaaf2472f 100644 --- a/src/lib/Client/Tools/APK.py +++ b/src/lib/Bcfg2/Client/Tools/APK.py @@ -1,5 +1,4 @@ """This provides Bcfg2 support for Alpine Linux APK packages.""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py index db8cd56e7..6b839ffbc 100644 --- a/src/lib/Client/Tools/APT.py +++ b/src/lib/Bcfg2/Client/Tools/APT.py @@ -1,5 +1,4 @@ """This is the Bcfg2 support for apt-get.""" -__revision__ = '$Revision$' # suppress apt API warnings import warnings diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index c089cde1d..dc49347e9 100644 --- a/src/lib/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -1,5 +1,4 @@ """Action driver""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools from Bcfg2.Client.Frame import matches_white_list, passes_black_list diff --git a/src/lib/Client/Tools/Blast.py b/src/lib/Bcfg2/Client/Tools/Blast.py index 29cdfa116..5d5e74ab2 100644 --- a/src/lib/Client/Tools/Blast.py +++ b/src/lib/Bcfg2/Client/Tools/Blast.py @@ -1,6 +1,4 @@ -# This is the bcfg2 support for blastwave packages (pkg-get) """This provides Bcfg2 support for Blastwave.""" -__revision__ = '$Revision$' import tempfile import Bcfg2.Client.Tools.SYSV diff --git a/src/lib/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py index 17e8bf09b..12ea5f132 100644 --- a/src/lib/Client/Tools/Chkconfig.py +++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py @@ -1,8 +1,6 @@ # This is the bcfg2 support for chkconfig -# $Id$ """This is chkconfig support.""" -__revision__ = '$Revision$' import os diff --git a/src/lib/Client/Tools/DebInit.py b/src/lib/Bcfg2/Client/Tools/DebInit.py index 022332602..ca6fc439e 100644 --- a/src/lib/Client/Tools/DebInit.py +++ b/src/lib/Bcfg2/Client/Tools/DebInit.py @@ -1,5 +1,4 @@ """Debian Init Support for Bcfg2""" -__revision__ = '$Revision$' import glob import os diff --git a/src/lib/Client/Tools/Encap.py b/src/lib/Bcfg2/Client/Tools/Encap.py index 92062a750..fa09c3ec7 100644 --- a/src/lib/Client/Tools/Encap.py +++ b/src/lib/Bcfg2/Client/Tools/Encap.py @@ -1,7 +1,5 @@ """Bcfg2 Support for Encap Packages""" -__revision__ = '$Revision$' - import glob import re import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/FreeBSDInit.py b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py index 10f0f2e93..10f0f2e93 100644 --- a/src/lib/Client/Tools/FreeBSDInit.py +++ b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py diff --git a/src/lib/Client/Tools/FreeBSDPackage.py b/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py index 04c05adaa..3e6f2b6bb 100644 --- a/src/lib/Client/Tools/FreeBSDPackage.py +++ b/src/lib/Bcfg2/Client/Tools/FreeBSDPackage.py @@ -1,5 +1,4 @@ """This is the Bcfg2 tool for the FreeBSD package system.""" -__revision__ = '$Rev$' # TODO # - actual package installation diff --git a/src/lib/Client/Tools/IPS.py b/src/lib/Bcfg2/Client/Tools/IPS.py index 9afd23143..e30bbd2a4 100644 --- a/src/lib/Client/Tools/IPS.py +++ b/src/lib/Bcfg2/Client/Tools/IPS.py @@ -1,5 +1,4 @@ """This is the Bcfg2 support for OpenSolaris packages.""" -__revision__ = '$Revision$' import pkg.client.image as image import pkg.client.progress as progress diff --git a/src/lib/Client/Tools/MacPorts.py b/src/lib/Bcfg2/Client/Tools/MacPorts.py index 2a7ba9eb9..9724fab57 100644 --- a/src/lib/Client/Tools/MacPorts.py +++ b/src/lib/Bcfg2/Client/Tools/MacPorts.py @@ -1,5 +1,4 @@ """This provides Bcfg2 support for macports packages.""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Bcfg2/Client/Tools/POSIX.py index 3591c33ad..0d67dbbab 100644 --- a/src/lib/Client/Tools/POSIX.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX.py @@ -1,5 +1,4 @@ """All POSIX Type client support for Bcfg2.""" -__revision__ = '$Revision$' import binascii from datetime import datetime @@ -21,7 +20,7 @@ import Bcfg2.Client.Tools import Bcfg2.Options from Bcfg2.Client import XML -log = logging.getLogger('posix') +log = logging.getLogger('POSIX') # map between dev_type attribute and stat constants device_map = {'block': stat.S_IFBLK, @@ -259,8 +258,8 @@ class POSIX(Bcfg2.Client.Tools.Tool): 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-lint.' % (entry.get('name'))) + self.logger.error("POSIX: Entry %s not completely specified. " + "Try running bcfg2-lint." % (entry.get('name'))) return False while len(entry.get('perms', '')) < 4: entry.set('perms', '0' + entry.get('perms', '')) @@ -268,15 +267,15 @@ class POSIX(Bcfg2.Client.Tools.Tool): ondisk = os.stat(entry.get('name')) except OSError: entry.set('current_exists', 'false') - self.logger.debug("%s %s does not exist" % + self.logger.info("POSIX: %s %s does not exist" % (entry.tag, entry.get('name'))) return False try: owner = str(ondisk[stat.ST_UID]) group = str(ondisk[stat.ST_GID]) except (OSError, KeyError): - self.logger.error('User/Group resolution failed for path %s' % \ - entry.get('name')) + self.logger.info("POSIX: User/Group resolution failed " + "for path %s" % entry.get('name')) owner = 'root' group = '0' finfo = os.stat(entry.get('name')) @@ -301,11 +300,11 @@ class POSIX(Bcfg2.Client.Tools.Tool): ex_ents = [e for e in entries if e not in modlist] if ex_ents: pruneTrue = False - self.logger.debug("Directory %s contains extra entries:" % \ - entry.get('name')) - self.logger.debug(ex_ents) + self.logger.info("POSIX: Directory %s contains " + "extra entries:" % entry.get('name')) + self.logger.info(ex_ents) nqtext = entry.get('qtext', '') + '\n' - nqtext += "Directory %s contains extra entries:" % \ + nqtext += "Directory %s contains extra entries: " % \ entry.get('name') nqtext += ":".join(ex_ents) entry.set('qtest', nqtext) @@ -496,7 +495,7 @@ class POSIX(Bcfg2.Client.Tools.Tool): (err.filename, err)) return False different = content != tempdata - + if different: if self.setup['interactive']: prompt = [entry.get('qtext', '')] diff --git a/src/lib/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py index c8c05061c..c8c05061c 100644 --- a/src/lib/Client/Tools/Pacman.py +++ b/src/lib/Bcfg2/Client/Tools/Pacman.py diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py new file mode 100644 index 000000000..4516f419d --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/Portage.py @@ -0,0 +1,125 @@ +"""This is the Bcfg2 tool for the Gentoo Portage system.""" + +import re +import Bcfg2.Client.Tools +from Bcfg2.Bcfg2Py3k import ConfigParser + + +class Portage(Bcfg2.Client.Tools.PkgTool): + """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')] + __req__ = {'Package': ['name', 'version']} + pkgtype = 'ebuild' + # requires a working PORTAGE_BINHOST in make.conf + _binpkgtool = ('emerge --getbinpkgonly %s', ('=%s-%s', \ + ['name', 'version'])) + pkgtool = ('emerge %s', ('=%s-%s', ['name', 'version'])) + + def __init__(self, logger, cfg, setup): + self._initialised = False + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, cfg, setup) + self._initialised = True + self.__important__ = self.__important__ + ['/etc/make.conf'] + self._pkg_pattern = re.compile('(.*)-(\d.*)') + self._ebuild_pattern = re.compile('(ebuild|binary)') + self.cfg = cfg + self.installed = {} + self._binpkgonly = True + + # Used to get options from configuration file + parser = ConfigParser.ConfigParser() + parser.read(self.setup.get('setup')) + for opt in ['binpkgonly']: + if parser.has_option(self.name, opt): + setattr(self, ('_%s' % opt), + self._StrToBoolIfBool(parser.get(self.name, opt))) + + if self._binpkgonly: + self.pkgtool = self._binpkgtool + self.RefreshPackages() + + def _StrToBoolIfBool(self, s): + """Returns a boolean if the string specifies a boolean value. + Returns a string otherwise""" + if s.lower() in ('true', 'yes', 't', 'y', '1'): + return True + elif s.lower() in ('false', 'no', 'f', 'n', '0'): + return False + else: + return s + + def RefreshPackages(self): + """Refresh memory hashes of packages.""" + if not self._initialised: + return + self.logger.info('Getting list of installed packages') + cache = self.cmd.run("equery -q list '*'")[1] + self.installed = {} + for pkg in cache: + if self._pkg_pattern.match(pkg): + name = self._pkg_pattern.match(pkg).group(1) + version = self._pkg_pattern.match(pkg).group(2) + self.installed[name] = version + else: + self.logger.info("Failed to parse pkg name %s" % pkg) + + def VerifyPackage(self, entry, modlist): + """Verify package for entry.""" + if not 'version' in entry.attrib: + self.logger.info("Cannot verify unversioned package %s" % + (entry.get('name'))) + return False + + if not (entry.get('name') in self.installed): + # Can't verify package that isn't installed + entry.set('current_exists', 'false') + return False + + # get the installed version + version = self.installed[entry.get('name')] + entry.set('current_version', version) + + if not self.setup['quick']: + if ('verify' not in entry.attrib) or \ + self._StrToBoolIfBool(entry.get('verify')): + + # Check the package if: + # - Not running in quick mode + # - No verify option is specified in the literal configuration + # OR + # - Verify option is specified and is true + + self.logger.debug('Running equery check on %s' % + entry.get('name')) + output = self.cmd.run("/usr/bin/equery -N check '=%s-%s' " + "2>&1 | grep '!!!' | awk '{print $2}'" + % ((entry.get('name'), version)))[1] + if [filename for filename in output \ + if filename not in modlist]: + return False + + # By now the package must be in one of the following states: + # - Not require checking + # - Have no files modified at all + # - Have modified files in the modlist only + if self.installed[entry.get('name')] == version: + # Specified package version is installed + # Specified package version may be any in literal configuration + return True + + # Something got skipped. Indicates a bug + return False + + def RemovePackages(self, packages): + """Deal with extra configuration detected.""" + pkgnames = " ".join([pkg.get('name') for pkg in packages]) + if len(packages) > 0: + self.logger.info('Removing packages:') + self.logger.info(pkgnames) + self.cmd.run("emerge --unmerge --quiet %s" % + " ".join(pkgnames.split(' '))) + self.RefreshPackages() + self.extra = self.FindExtraPackages() diff --git a/src/lib/Client/Tools/RPMng.py b/src/lib/Bcfg2/Client/Tools/RPMng.py index 5376118c2..00dd00d71 100644 --- a/src/lib/Client/Tools/RPMng.py +++ b/src/lib/Bcfg2/Client/Tools/RPMng.py @@ -1,7 +1,5 @@ """Bcfg2 Support for RPMS""" -__revision__ = '$Revision$' - import os.path import rpm import rpmtools @@ -9,12 +7,6 @@ import Bcfg2.Client.Tools # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - class RPMng(Bcfg2.Client.Tools.PkgTool): """Support for RPM packages.""" name = 'RPMng' @@ -461,7 +453,7 @@ class RPMng(Bcfg2.Client.Tools.PkgTool): packages is a list of Package Entries with Instances generated by FindExtraPackages(). - + """ self.logger.debug('Running RPMng.RemovePackages()') diff --git a/src/lib/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py index d832d98a8..1b9a29478 100644 --- a/src/lib/Client/Tools/RcUpdate.py +++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py @@ -1,5 +1,4 @@ """This is rc-update support.""" -__revision__ = '$Revision$' import os import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/SMF.py b/src/lib/Bcfg2/Client/Tools/SMF.py index 944408326..f824410ad 100644 --- a/src/lib/Client/Tools/SMF.py +++ b/src/lib/Bcfg2/Client/Tools/SMF.py @@ -1,5 +1,4 @@ """SMF support for Bcfg2""" -__revision__ = '$Revision$' import glob import os diff --git a/src/lib/Client/Tools/SYSV.py b/src/lib/Bcfg2/Client/Tools/SYSV.py index b5e1f1c59..eb4a13dfb 100644 --- a/src/lib/Client/Tools/SYSV.py +++ b/src/lib/Bcfg2/Client/Tools/SYSV.py @@ -1,6 +1,4 @@ -# This is the bcfg2 support for solaris sysv packages """This provides bcfg2 support for Solaris SYSV packages.""" -__revision__ = '$Revision$' import tempfile diff --git a/src/lib/Client/Tools/Systemd.py b/src/lib/Bcfg2/Client/Tools/Systemd.py index e3f6a4169..e3f6a4169 100644 --- a/src/lib/Client/Tools/Systemd.py +++ b/src/lib/Bcfg2/Client/Tools/Systemd.py diff --git a/src/lib/Client/Tools/Upstart.py b/src/lib/Bcfg2/Client/Tools/Upstart.py index 41a585c23..7afc8edd7 100644 --- a/src/lib/Client/Tools/Upstart.py +++ b/src/lib/Bcfg2/Client/Tools/Upstart.py @@ -1,5 +1,4 @@ """Upstart support for Bcfg2.""" -__revision__ = '$Revision$' import glob import re diff --git a/src/lib/Client/Tools/VCS.py b/src/lib/Bcfg2/Client/Tools/VCS.py index e6081dc1c..e6081dc1c 100644 --- a/src/lib/Client/Tools/VCS.py +++ b/src/lib/Bcfg2/Client/Tools/VCS.py diff --git a/src/lib/Client/Tools/YUM24.py b/src/lib/Bcfg2/Client/Tools/YUM24.py index 66768fb34..4e488b9da 100644 --- a/src/lib/Client/Tools/YUM24.py +++ b/src/lib/Bcfg2/Client/Tools/YUM24.py @@ -1,5 +1,4 @@ """This provides bcfg2 support for yum.""" -__revision__ = '$Revision: $' import copy import os.path @@ -10,12 +9,6 @@ import Bcfg2.Client.Tools.RPMng # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - YAD = True CP = ConfigParser.ConfigParser() try: diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Bcfg2/Client/Tools/YUMng.py index 9b999df92..244b66cf4 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Bcfg2/Client/Tools/YUMng.py @@ -1,5 +1,4 @@ """This provides bcfg2 support for yum.""" -__revision__ = '$Revision$' import copy import os.path @@ -16,12 +15,6 @@ import Bcfg2.Client.Tools # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - def build_yname(pkgname, inst): """Build yum appropriate package name.""" @@ -760,7 +753,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool): else: self.logger.error("Second pass yum install failed.") self.logger.debug(" %s" % restring) - except yum.Errors.YumBaseError, e: + except yum.Errors.YumBaseError: + e = sys.exc_info()[1] self.logger.error("Yum transaction error: %s" % str(e)) self.yb.conf.skip_broken = skipBroken diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index e879d7dd6..c6cb6e239 100644 --- a/src/lib/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -1,8 +1,4 @@ """This contains all Bcfg2 Tool modules""" -# suppress popen2 warnings for python 2.3 -import warnings -warnings.filterwarnings("ignore", "The popen2 module is deprecated.*", - DeprecationWarning) import os import stat import sys @@ -10,7 +6,6 @@ from subprocess import Popen, PIPE import time import Bcfg2.Client.XML -__revision__ = '$Revision$' __all__ = [tool.split('.')[0] \ for tool in os.listdir(os.path.dirname(__file__)) \ diff --git a/src/lib/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py index d7cbfa07f..c022d32ae 100644 --- a/src/lib/Client/Tools/launchd.py +++ b/src/lib/Bcfg2/Client/Tools/launchd.py @@ -1,5 +1,4 @@ """launchd support for Bcfg2.""" -__revision__ = '$Revision$' import os diff --git a/src/lib/Client/Tools/rpmtools.py b/src/lib/Bcfg2/Client/Tools/rpmtools.py index 3cd2b7014..7441b2c06 100755 --- a/src/lib/Client/Tools/rpmtools.py +++ b/src/lib/Bcfg2/Client/Tools/rpmtools.py @@ -18,7 +18,6 @@ Run 'rpmtools' -h for the options. """ -__revision__ = '$Revision$' import grp import optparse diff --git a/src/lib/Client/XML.py b/src/lib/Bcfg2/Client/XML.py index 42b1017ac..858479611 100644 --- a/src/lib/Client/XML.py +++ b/src/lib/Bcfg2/Client/XML.py @@ -1,5 +1,4 @@ '''XML lib compatibility layer for the Bcfg2 client''' -__revision__ = '$Revision$' # library will use lxml, then builtin xml.etree, then ElementTree diff --git a/src/lib/Client/__init__.py b/src/lib/Bcfg2/Client/__init__.py index ea60a4259..6ed37b257 100644 --- a/src/lib/Client/__init__.py +++ b/src/lib/Bcfg2/Client/__init__.py @@ -1,4 +1,3 @@ """This contains all Bcfg2 Client modules""" -__revision__ = '$Revision$' __all__ = ["Frame", "Tools", "XML"] diff --git a/src/lib/Component.py b/src/lib/Bcfg2/Component.py index caea3eda9..eb9ea166a 100644 --- a/src/lib/Component.py +++ b/src/lib/Bcfg2/Component.py @@ -1,7 +1,5 @@ """Cobalt component base.""" -__revision__ = '$Revision$' - __all__ = ["Component", "exposed", "automatic", "run_component"] import inspect @@ -16,7 +14,7 @@ import Bcfg2.Logger from Bcfg2.Statistics import Statistics from Bcfg2.SSLServer import XMLRPCServer # Compatibility import -from Bcfg2.Bcfg2Py3k import xmlrpclib, urlparse, fprint +from Bcfg2.Bcfg2Py3k import xmlrpclib, urlparse logger = logging.getLogger() @@ -57,7 +55,7 @@ def run_component(component_cls, listen_all, location, daemon, pidfile_name, os.chdir(os.sep) pidfile = open(pidfile_name or "/dev/null", "w") - fprint(os.getpid(), pidfile) + pidfile.write("%s\n" % os.getpid()) pidfile.close() component = component_cls(cfile=cfile, **cls_kwargs) diff --git a/src/lib/Logger.py b/src/lib/Bcfg2/Logger.py index e8f9ecd47..81b45550f 100644 --- a/src/lib/Logger.py +++ b/src/lib/Bcfg2/Logger.py @@ -1,5 +1,4 @@ """Bcfg2 logging support""" -__revision__ = '$Revision$' import copy import fcntl @@ -96,9 +95,9 @@ class FragmentingSysLogHandler(logging.handlers.SysLogHandler): else: msgs = [record] for newrec in msgs: - msg = self.log_format_string % (self.encodePriority(self.facility, - newrec.levelname.lower()), - self.format(newrec)) + msg = '<%d>%s\000' % (self.encodePriority(self.facility, + newrec.levelname.lower()), + self.format(newrec)) try: self.socket.send(msg.encode('ascii')) except socket.error: diff --git a/src/lib/Options.py b/src/lib/Bcfg2/Options.py index b55a8a55a..2038af3bb 100644 --- a/src/lib/Options.py +++ b/src/lib/Bcfg2/Options.py @@ -1,7 +1,7 @@ """Option parsing library for utilities.""" -__revision__ = '$Revision$' import getopt +import re import os import sys import shlex @@ -21,17 +21,40 @@ class OptionFailure(Exception): DEFAULT_CONFIG_LOCATION = '/etc/bcfg2.conf' #/etc/bcfg2.conf DEFAULT_INSTALL_PREFIX = '/usr' #/usr -class Option(object): - cfpath = DEFAULT_CONFIG_LOCATION - __cfp = False +class DefaultConfigParser(ConfigParser.ConfigParser): + def get(self, section, option, **kwargs): + """ convenience method for getting config items """ + default = None + if 'default' in kwargs: + default = kwargs['default'] + del kwargs['default'] + try: + return ConfigParser.ConfigParser.get(self, section, option, + **kwargs) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + if default is not None: + return default + else: + raise + + def getboolean(self, section, option, **kwargs): + """ convenience method for getting boolean config items """ + default = None + if 'default' in kwargs: + default = kwargs['default'] + del kwargs['default'] + try: + return ConfigParser.ConfigParser.getboolean(self, section, + option, **kwargs) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, + ValueError): + if default is not None: + return default + else: + raise - def getCFP(self): - if not self.__cfp: - self.__cfp = ConfigParser.ConfigParser() - self.__cfp.readfp(open(self.cfpath)) - return self.__cfp - cfp = property(getCFP) +class Option(object): def get_cooked_value(self, value): if self.boolean: return True @@ -93,7 +116,7 @@ class Option(object): else: return self.cmd[2:] - def parse(self, opts, rawopts): + def parse(self, opts, rawopts, configparser=None): if self.cmd and opts: # Processing getopted data optinfo = [opt[1] for opt in opts if opt[0] == self.cmd] @@ -111,31 +134,45 @@ class Option(object): if self.env and self.env in os.environ: self.value = self.get_cooked_value(os.environ[self.env]) return - if self.cf: - # FIXME: This is potentially masking a lot of errors + if self.cf and configparser: try: - self.value = self.get_cooked_value(self.cfp.get(*self.cf)) + self.value = self.get_cooked_value(configparser.get(*self.cf)) return - except: + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): pass # Default value not cooked self.value = self.default class OptionSet(dict): - def __init__(self, *args): + def __init__(self, *args, **kwargs): dict.__init__(self, *args) self.hm = self.buildHelpMessage() + if 'configfile' in kwargs: + self.cfile = kwargs['configfile'] + else: + self.cfile = DEFAULT_CONFIG_LOCATION + self.cfp = DefaultConfigParser() + if (len(self.cfp.read(self.cfile)) == 0 and + ('quiet' not in kwargs or not kwargs['quiet'])): + print("Warning! Unable to read specified configuration file: %s" % + self.cfile) def buildGetopt(self): return ''.join([opt.buildGetopt() for opt in list(self.values())]) def buildLongGetopt(self): - return [opt.buildLongGetopt() for opt in list(self.values()) if opt.long] + return [opt.buildLongGetopt() for opt in list(self.values()) + if opt.long] def buildHelpMessage(self): if hasattr(self, 'hm'): return self.hm - return ' '.join([opt.buildHelpMessage() for opt in list(self.values())]) + hlist = [] # list of _non-empty_ help messages + for opt in list(self.values()): + hm = opt.buildHelpMessage() + if hm != '': + hlist.append(hm) + return ' '.join(hlist) def helpExit(self, msg='', code=1): if msg: @@ -160,15 +197,17 @@ class OptionSet(dict): continue option = self[key] if do_getopt: - option.parse(opts, []) + option.parse(opts, [], configparser=self.cfp) else: - option.parse([], argv) + option.parse([], argv, configparser=self.cfp) if hasattr(option, 'value'): val = option.value self[key] = val -list_split = lambda x:x.replace(' ','').split(',') -flist_split = lambda x:list_split(x.replace(':', '').lower()) +def list_split(c_string): + if c_string: + return re.split("\s*,\s*", c_string) + return [] def colon_split(c_string): if c_string: @@ -243,7 +282,7 @@ MDATA_PERMS = Option('Default Path permissions', '644', cf=('mdata', 'perms'), odesc='octal permissions') MDATA_PARANOID = Option('Default Path paranoid setting', - 'false', cf=('mdata', 'paranoid'), + 'true', cf=('mdata', 'paranoid'), odesc='Path paranoid setting') MDATA_SENSITIVE = Option('Default Path sensitive setting', 'false', cf=('mdata', 'sensitive'), @@ -372,21 +411,43 @@ CLIENT_SYSTEM_ETC_PATH = Option('System etc path', cf=('APT', 'etc_path'), LOGGING_FILE_PATH = Option('Set path of file log', default=None, cmd='-o', odesc='<path>', cf=('logging', 'path')) +# Plugin-specific options +CFG_VALIDATION = Option('Run validation on Cfg files', default=True, + cf=('cfg', 'validation'), cmd='--cfg-validation', + long_arg=True, cook=get_bool) + class OptionParser(OptionSet): """ OptionParser bootstraps option parsing, getting the value of the config file """ def __init__(self, args): - self.Bootstrap = OptionSet([('configfile', CFILE)]) + self.Bootstrap = OptionSet([('configfile', CFILE)], quiet=True) self.Bootstrap.parse(sys.argv[1:], do_getopt=False) - if self.Bootstrap['configfile'] != Option.cfpath: - Option.cfpath = self.Bootstrap['configfile'] - Option.__cfp = False - OptionSet.__init__(self, args) - try: - f = open(Option.cfpath, 'r') - f.close() - except IOError: - e = sys.exc_info()[1] - print("Warning! Unable to read specified configuration file: %s" % e) + OptionSet.__init__(self, args, configfile=self.Bootstrap['configfile']) + self.optinfo = args + + def HandleEvent(self, event): + if 'configfile' not in self or not isinstance(self['configfile'], str): + # we haven't parsed options yet, or CFILE wasn't included + # in the options + return + if event.filename != self['configfile']: + print("Got event for unknown file: %s" % event.filename) + return + if event.code2str() == 'deleted': + return + self.reparse() + + def reparse(self): + for key, opt in self.optinfo.items(): + self[key] = opt + if "args" not in self.optinfo: + del self['args'] + self.parse(self.argv, self.do_getopt) + + def parse(self, argv, do_getopt=True): + self.argv = argv + self.do_getopt = do_getopt + OptionSet.parse(self, self.argv, do_getopt=self.do_getopt) + diff --git a/src/lib/Proxy.py b/src/lib/Bcfg2/Proxy.py index fe2b18de8..422d642db 100644 --- a/src/lib/Proxy.py +++ b/src/lib/Bcfg2/Proxy.py @@ -8,9 +8,6 @@ load_config -- read configuration files """ -__revision__ = '$Revision: $' - - import logging import re import socket @@ -315,7 +312,7 @@ class XMLRPCTransport(xmlrpclib.Transport): 408, str(err), self._extra_headers)) - + if errcode != 200: raise ProxyError(xmlrpclib.ProtocolError(host + handler, errcode, diff --git a/src/lib/SSLServer.py b/src/lib/Bcfg2/SSLServer.py index 32ab9933b..6aa46ea58 100644 --- a/src/lib/SSLServer.py +++ b/src/lib/Bcfg2/SSLServer.py @@ -1,7 +1,5 @@ """Bcfg2 SSL server.""" -__revision__ = '$Revision$' - __all__ = [ "SSLServer", "XMLRPCRequestHandler", "XMLRPCServer", ] @@ -16,7 +14,6 @@ import logging import ssl import threading import time -import types # Compatibility imports from Bcfg2.Bcfg2Py3k import xmlrpclib, SimpleXMLRPCServer, SocketServer @@ -48,7 +45,7 @@ class XMLRPCDispatcher (SimpleXMLRPCServer.SimpleXMLRPCDispatcher): params = (address, ) + params response = self.instance._dispatch(method, params, self.funcs) # py3k compatibility - if type(response) not in [bool, str, list, dict, types.NoneType]: + if type(response) not in [bool, str, list, dict] or response is None: response = (response.decode('utf-8'), ) else: response = (response, ) diff --git a/src/lib/Server/Admin/Backup.py b/src/lib/Bcfg2/Server/Admin/Backup.py index 3744abca3..3744abca3 100644 --- a/src/lib/Server/Admin/Backup.py +++ b/src/lib/Bcfg2/Server/Admin/Backup.py diff --git a/src/lib/Server/Admin/Bundle.py b/src/lib/Bcfg2/Server/Admin/Bundle.py index 89c099602..89c099602 100644 --- a/src/lib/Server/Admin/Bundle.py +++ b/src/lib/Bcfg2/Server/Admin/Bundle.py diff --git a/src/lib/Server/Admin/Client.py b/src/lib/Bcfg2/Server/Admin/Client.py index 4d580c54c..4d580c54c 100644 --- a/src/lib/Server/Admin/Client.py +++ b/src/lib/Bcfg2/Server/Admin/Client.py diff --git a/src/lib/Server/Admin/Compare.py b/src/lib/Bcfg2/Server/Admin/Compare.py index 050dd69f8..050dd69f8 100644 --- a/src/lib/Server/Admin/Compare.py +++ b/src/lib/Bcfg2/Server/Admin/Compare.py diff --git a/src/lib/Server/Admin/Group.py b/src/lib/Bcfg2/Server/Admin/Group.py index 16a773d6f..16a773d6f 100644 --- a/src/lib/Server/Admin/Group.py +++ b/src/lib/Bcfg2/Server/Admin/Group.py diff --git a/src/lib/Server/Admin/Init.py b/src/lib/Bcfg2/Server/Admin/Init.py index 832190b7d..c1f9ed484 100644 --- a/src/lib/Server/Admin/Init.py +++ b/src/lib/Bcfg2/Server/Admin/Init.py @@ -55,6 +55,7 @@ groups = '''<Groups version='3.0'> <Group name='suse'/> <Group name='mandrake'/> <Group name='solaris'/> + <Group name='arch'/> </Groups> ''' @@ -71,7 +72,8 @@ os_list = [('Red Hat/Fedora/RHEL/RHAS/Centos', 'redhat'), ('Debian', 'debian'), ('Ubuntu', 'ubuntu'), ('Gentoo', 'gentoo'), - ('FreeBSD', 'freebsd')] + ('FreeBSD', 'freebsd'), + ('Arch', 'arch')] # Complete list of plugins plugin_list = ['Account', diff --git a/src/lib/Server/Admin/Minestruct.py b/src/lib/Bcfg2/Server/Admin/Minestruct.py index b929a9a8c..b929a9a8c 100644 --- a/src/lib/Server/Admin/Minestruct.py +++ b/src/lib/Bcfg2/Server/Admin/Minestruct.py diff --git a/src/lib/Server/Admin/Perf.py b/src/lib/Bcfg2/Server/Admin/Perf.py index 411442698..411442698 100644 --- a/src/lib/Server/Admin/Perf.py +++ b/src/lib/Bcfg2/Server/Admin/Perf.py diff --git a/src/lib/Server/Admin/Pull.py b/src/lib/Bcfg2/Server/Admin/Pull.py index daf353107..daf353107 100644 --- a/src/lib/Server/Admin/Pull.py +++ b/src/lib/Bcfg2/Server/Admin/Pull.py diff --git a/src/lib/Server/Admin/Query.py b/src/lib/Bcfg2/Server/Admin/Query.py index 3dd326645..3dd326645 100644 --- a/src/lib/Server/Admin/Query.py +++ b/src/lib/Bcfg2/Server/Admin/Query.py diff --git a/src/lib/Server/Admin/Reports.py b/src/lib/Bcfg2/Server/Admin/Reports.py index 974cdff9d..974cdff9d 100644 --- a/src/lib/Server/Admin/Reports.py +++ b/src/lib/Bcfg2/Server/Admin/Reports.py diff --git a/src/lib/Server/Admin/Snapshots.py b/src/lib/Bcfg2/Server/Admin/Snapshots.py index 8bc56f1f1..8bc56f1f1 100644 --- a/src/lib/Server/Admin/Snapshots.py +++ b/src/lib/Bcfg2/Server/Admin/Snapshots.py diff --git a/src/lib/Server/Admin/Tidy.py b/src/lib/Bcfg2/Server/Admin/Tidy.py index 82319b93e..82319b93e 100644 --- a/src/lib/Server/Admin/Tidy.py +++ b/src/lib/Bcfg2/Server/Admin/Tidy.py diff --git a/src/lib/Server/Admin/Viz.py b/src/lib/Bcfg2/Server/Admin/Viz.py index 2faa423c1..2faa423c1 100644 --- a/src/lib/Server/Admin/Viz.py +++ b/src/lib/Bcfg2/Server/Admin/Viz.py diff --git a/src/lib/Server/Admin/Xcmd.py b/src/lib/Bcfg2/Server/Admin/Xcmd.py index 140465468..140465468 100644 --- a/src/lib/Server/Admin/Xcmd.py +++ b/src/lib/Bcfg2/Server/Admin/Xcmd.py diff --git a/src/lib/Server/Admin/__init__.py b/src/lib/Bcfg2/Server/Admin/__init__.py index a9f0b8cd6..618fa450e 100644 --- a/src/lib/Server/Admin/__init__.py +++ b/src/lib/Bcfg2/Server/Admin/__init__.py @@ -1,5 +1,3 @@ -__revision__ = '$Revision$' - __all__ = [ 'Backup', 'Bundle', @@ -108,7 +106,7 @@ class MetadataCore(Mode): """Base class for admin-modes that handle metadata.""" __plugin_whitelist__ = None __plugin_blacklist__ = None - + def __init__(self, setup): Mode.__init__(self, setup) if self.__plugin_whitelist__ is not None: @@ -124,7 +122,8 @@ class MetadataCore(Mode): setup['plugins'], setup['password'], setup['encoding'], - filemonitor=setup['filemonitor']) + filemonitor=setup['filemonitor'], + setup=setup) if setup['event debug']: self.bcore.fam.debug = True except Bcfg2.Server.Core.CoreInitError: diff --git a/src/lib/Server/Core.py b/src/lib/Bcfg2/Server/Core.py index 9fa42cfd1..d42c5ad4f 100644 --- a/src/lib/Server/Core.py +++ b/src/lib/Bcfg2/Server/Core.py @@ -1,5 +1,4 @@ """Bcfg2.Server.Core provides the runtime support for Bcfg2 modules.""" -__revision__ = '$Revision$' import atexit import logging @@ -44,7 +43,10 @@ def sort_xml(node, key=None): for child in node: sort_xml(child, key) - sorted_children = sorted(node, key=key) + try: + sorted_children = sorted(node, key=key) + except TypeError: + sorted_children = node node[:] = sorted_children @@ -61,7 +63,7 @@ class Core(Component): implementation = 'bcfg2-server' def __init__(self, repo, plugins, password, encoding, - cfile='/etc/bcfg2.conf', ca=None, + cfile='/etc/bcfg2.conf', ca=None, setup=None, filemonitor='default', start_fam_thread=False): Component.__init__(self) self.datastore = repo @@ -84,6 +86,7 @@ class Core(Component): self.revision = '-1' self.password = password self.encoding = encoding + self.setup = setup atexit.register(self.shutdown) # Create an event to signal worker threads to shutdown self.terminate = threading.Event() @@ -130,6 +133,11 @@ class Core(Component): self.fam_thread = threading.Thread(target=self._file_monitor_thread) if start_fam_thread: self.fam_thread.start() + self.monitor_cfile() + + def monitor_cfile(self): + if self.setup: + self.fam.AddMonitor(self.cfile, self.setup) def plugins_by_type(self, base_cls): """Return a list of loaded plugins that match the passed type. @@ -241,12 +249,14 @@ class Core(Component): continue try: self.Bind(entry, metadata) - except PluginExecutionError, exc: + except PluginExecutionError: + exc = sys.exc_info()[1] if 'failure' not in entry.attrib: entry.set('failure', 'bind error: %s' % format_exc()) - logger.error("Failed to bind entry: %s %s" % \ - (entry.tag, entry.get('name'))) - except Exception, exc: + logger.error("Failed to bind entry %s:%s: %s" % + (entry.tag, entry.get('name'), exc)) + except Exception: + exc = sys.exc_info()[1] if 'failure' not in entry.attrib: entry.set('failure', 'bind error: %s' % format_exc()) logger.error("Unexpected failure in BindStructure: %s %s" \ @@ -327,7 +337,7 @@ class Core(Component): except: logger.error("error in BindStructure", exc_info=1) self.validate_goals(meta, config) - + sort_xml(config, key=lambda e: e.get('name')) logger.info("Generated config for %s in %.03f seconds" % \ diff --git a/src/lib/Server/FileMonitor.py b/src/lib/Bcfg2/Server/FileMonitor.py index d6b313e6b..d6b313e6b 100644 --- a/src/lib/Server/FileMonitor.py +++ b/src/lib/Bcfg2/Server/FileMonitor.py diff --git a/src/lib/Server/Hostbase/.gitignore b/src/lib/Bcfg2/Server/Hostbase/.gitignore index 8e15b5395..8e15b5395 100644 --- a/src/lib/Server/Hostbase/.gitignore +++ b/src/lib/Bcfg2/Server/Hostbase/.gitignore diff --git a/src/lib/Server/Hostbase/__init__.py b/src/lib/Bcfg2/Server/Hostbase/__init__.py index e69de29bb..e69de29bb 100644 --- a/src/lib/Server/Hostbase/__init__.py +++ b/src/lib/Bcfg2/Server/Hostbase/__init__.py diff --git a/src/lib/Server/Hostbase/backends.py b/src/lib/Bcfg2/Server/Hostbase/backends.py index bf774f695..ecaf3c109 100644 --- a/src/lib/Server/Hostbase/backends.py +++ b/src/lib/Bcfg2/Server/Hostbase/backends.py @@ -2,8 +2,6 @@ from django.contrib.auth.models import User #from ldapauth import * from nisauth import * -__revision__ = '$Revision$' - ## class LDAPBackend(object): ## def authenticate(self,username=None,password=None): diff --git a/src/lib/Server/Hostbase/hostbase/__init__.py b/src/lib/Bcfg2/Server/Hostbase/hostbase/__init__.py index e69de29bb..e69de29bb 100644 --- a/src/lib/Server/Hostbase/hostbase/__init__.py +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/__init__.py diff --git a/src/lib/Server/Hostbase/hostbase/admin.py b/src/lib/Bcfg2/Server/Hostbase/hostbase/admin.py index 70a2233cc..70a2233cc 100644 --- a/src/lib/Server/Hostbase/hostbase/admin.py +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/admin.py diff --git a/src/lib/Server/Hostbase/hostbase/models.py b/src/lib/Bcfg2/Server/Hostbase/hostbase/models.py index 3f08a09a0..3f08a09a0 100644 --- a/src/lib/Server/Hostbase/hostbase/models.py +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/models.py diff --git a/src/lib/Server/Hostbase/hostbase/sql/zone.sql b/src/lib/Bcfg2/Server/Hostbase/hostbase/sql/zone.sql index b78187ab2..b78187ab2 100644 --- a/src/lib/Server/Hostbase/hostbase/sql/zone.sql +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/sql/zone.sql diff --git a/src/lib/Server/Hostbase/hostbase/urls.py b/src/lib/Bcfg2/Server/Hostbase/hostbase/urls.py index 0ee204abe..0ee204abe 100644 --- a/src/lib/Server/Hostbase/hostbase/urls.py +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/urls.py diff --git a/src/lib/Server/Hostbase/hostbase/views.py b/src/lib/Bcfg2/Server/Hostbase/hostbase/views.py index ff1d4710d..57ef5eff8 100644 --- a/src/lib/Server/Hostbase/hostbase/views.py +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/views.py @@ -2,8 +2,6 @@ Contains all the views associated with the hostbase app Also has does form validation """ -__revision__ = "$Revision: $" - from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/base.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/base.html index 1d7c5565b..1d7c5565b 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/base.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/base.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/confirm.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/confirm.html index ca8b0cc07..ca8b0cc07 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/confirm.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/confirm.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/copy.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/copy.html index 400ef58f2..400ef58f2 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/copy.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/copy.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/dns.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dns.html index da179e5a1..da179e5a1 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/dns.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dns.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/dnsedit.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dnsedit.html index 18bd7ab83..b1b71ab67 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/dnsedit.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/dnsedit.html @@ -73,7 +73,7 @@ <td> <input name="{{ name.id }}priority" type="text" size="6"> <input name="{{ name.id }}mx" type="text"></td></tr> {% endfor %} - <tr> <td> <b>name</b></td> + <tr> <td> <b>name</b></td> <td> <input name="{{ ip.0.ip_addr }}name" type="text"> <select name="{{ ip.0.ip_addr }}dns_view"> {% for choice in DNS_CHOICES %} @@ -86,7 +86,7 @@ <td> <input name="{{ ip.0.ip_addr }}priority" type="text" size="6"> <input name="{{ ip.0.ip_addr }}mx" type="text"></td></tr> <tr><td></td></tr> - <tr><td><hr></td><td><hr></td></tr> + <tr><td><hr></td><td><hr></td></tr> {% endifequal %} {% endfor %} {% endfor %} diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/edit.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/edit.html index 961c9d143..961c9d143 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/edit.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/edit.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/errors.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/errors.html index e5429b86c..e5429b86c 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/errors.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/errors.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/host.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/host.html index d6b8873bc..d6b8873bc 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/host.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/host.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html index 551bf3254..b5d794b50 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/host_confirm_delete.html @@ -24,7 +24,7 @@ <col width="150"> <col width="*"> <tr> <td> <b>hostname</b></td> - <td> {{ object.hostname }}</td></tr> + <td> {{ object.hostname }}</td></tr> <tr> <td> <b>whatami</b></td> <td> {{ object.whatami }}</td></tr> <tr> <td> <b>netgroup</b></td> diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html index aa9679cbd..aa9679cbd 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/log_detail.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/index.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/index.html index 92258b648..92258b648 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/index.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/index.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/login.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/login.html index ec24a0fc0..ec24a0fc0 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/login.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/login.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/logout.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.html index 994f631a8..994f631a8 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/logout.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/logout.tmpl b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.tmpl index e71e90e76..e71e90e76 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/logout.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logout.tmpl diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/logviewer.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logviewer.html index 806ccd63d..806ccd63d 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/logviewer.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/logviewer.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/navbar.tmpl b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/navbar.tmpl index 877d427d0..877d427d0 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/navbar.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/navbar.tmpl diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/new.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/new.html index 2dcd6271f..2dcd6271f 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/new.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/new.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/remove.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/remove.html index 4329200dd..4329200dd 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/remove.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/remove.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/results.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/results.html index 45b22058d..45b22058d 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/results.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/results.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/search.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/search.html index 409d418fe..409d418fe 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/search.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/search.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/zoneedit.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneedit.html index ee355ee87..ee355ee87 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/zoneedit.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneedit.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/zonenew.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zonenew.html index b59fa9e3c..b59fa9e3c 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/zonenew.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zonenew.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/zones.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zones.html index c773e7922..c773e7922 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/zones.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zones.html diff --git a/src/lib/Server/Hostbase/hostbase/webtemplates/zoneview.html b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneview.html index fa12e3ec5..fa12e3ec5 100644 --- a/src/lib/Server/Hostbase/hostbase/webtemplates/zoneview.html +++ b/src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/zoneview.html diff --git a/src/lib/Server/Hostbase/ldapauth.py b/src/lib/Bcfg2/Server/Hostbase/ldapauth.py index f3db26f67..f3db26f67 100644 --- a/src/lib/Server/Hostbase/ldapauth.py +++ b/src/lib/Bcfg2/Server/Hostbase/ldapauth.py diff --git a/src/lib/Server/Hostbase/manage.py b/src/lib/Bcfg2/Server/Hostbase/manage.py index 5e78ea979..5e78ea979 100755 --- a/src/lib/Server/Hostbase/manage.py +++ b/src/lib/Bcfg2/Server/Hostbase/manage.py diff --git a/src/lib/Server/Hostbase/media/base.css b/src/lib/Bcfg2/Server/Hostbase/media/base.css index ddbf02165..ddbf02165 100644 --- a/src/lib/Server/Hostbase/media/base.css +++ b/src/lib/Bcfg2/Server/Hostbase/media/base.css diff --git a/src/lib/Server/Hostbase/media/boxypastel.css b/src/lib/Bcfg2/Server/Hostbase/media/boxypastel.css index 7ae0684ef..7ae0684ef 100644 --- a/src/lib/Server/Hostbase/media/boxypastel.css +++ b/src/lib/Bcfg2/Server/Hostbase/media/boxypastel.css diff --git a/src/lib/Server/Hostbase/media/global.css b/src/lib/Bcfg2/Server/Hostbase/media/global.css index 73451e1bc..73451e1bc 100644 --- a/src/lib/Server/Hostbase/media/global.css +++ b/src/lib/Bcfg2/Server/Hostbase/media/global.css diff --git a/src/lib/Server/Hostbase/media/layout.css b/src/lib/Bcfg2/Server/Hostbase/media/layout.css index 9085cc220..9085cc220 100644 --- a/src/lib/Server/Hostbase/media/layout.css +++ b/src/lib/Bcfg2/Server/Hostbase/media/layout.css diff --git a/src/lib/Server/Hostbase/nisauth.py b/src/lib/Bcfg2/Server/Hostbase/nisauth.py index 9c7da8c0a..ae4c6c021 100644 --- a/src/lib/Server/Hostbase/nisauth.py +++ b/src/lib/Bcfg2/Server/Hostbase/nisauth.py @@ -1,10 +1,8 @@ +"""Checks with NIS to see if the current user is in the support group""" import os import crypt, nis from Bcfg2.Server.Hostbase.settings import AUTHORIZED_GROUP -"""Checks with NIS to see if the current user is in the support group""" - -__revision__ = "$Revision: $" class NISAUTHError(Exception): """NISAUTHError is raised when somehting goes boom.""" diff --git a/src/lib/Server/Hostbase/regex.py b/src/lib/Bcfg2/Server/Hostbase/regex.py index 41cc0f6f0..41cc0f6f0 100644 --- a/src/lib/Server/Hostbase/regex.py +++ b/src/lib/Bcfg2/Server/Hostbase/regex.py diff --git a/src/lib/Server/Hostbase/settings.py b/src/lib/Bcfg2/Server/Hostbase/settings.py index 4e641f13c..4e641f13c 100644 --- a/src/lib/Server/Hostbase/settings.py +++ b/src/lib/Bcfg2/Server/Hostbase/settings.py diff --git a/src/lib/Server/Hostbase/templates/batchadd.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/batchadd.tmpl index 74ea3c047..74ea3c047 100644 --- a/src/lib/Server/Hostbase/templates/batchadd.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/batchadd.tmpl diff --git a/src/lib/Server/Hostbase/templates/dhcpd.conf.head b/src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.conf.head index a3d19547e..a3d19547e 100644 --- a/src/lib/Server/Hostbase/templates/dhcpd.conf.head +++ b/src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.conf.head diff --git a/src/lib/Server/Hostbase/templates/dhcpd.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.tmpl index 757b263cd..757b263cd 100644 --- a/src/lib/Server/Hostbase/templates/dhcpd.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/dhcpd.tmpl diff --git a/src/lib/Server/Hostbase/templates/hosts.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/hosts.tmpl index 251cb5a79..251cb5a79 100644 --- a/src/lib/Server/Hostbase/templates/hosts.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/hosts.tmpl diff --git a/src/lib/Server/Hostbase/templates/hostsappend.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/hostsappend.tmpl index 00e0d5d04..00e0d5d04 100644 --- a/src/lib/Server/Hostbase/templates/hostsappend.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/hostsappend.tmpl diff --git a/src/lib/Server/Hostbase/templates/named.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/named.tmpl index 03e054198..03e054198 100644 --- a/src/lib/Server/Hostbase/templates/named.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/named.tmpl diff --git a/src/lib/Server/Hostbase/templates/namedviews.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/namedviews.tmpl index 52021620e..52021620e 100644 --- a/src/lib/Server/Hostbase/templates/namedviews.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/namedviews.tmpl diff --git a/src/lib/Server/Hostbase/templates/reverseappend.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/reverseappend.tmpl index 6ed520c98..6ed520c98 100644 --- a/src/lib/Server/Hostbase/templates/reverseappend.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/reverseappend.tmpl diff --git a/src/lib/Server/Hostbase/templates/reversesoa.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/reversesoa.tmpl index d142eaf7f..d142eaf7f 100644 --- a/src/lib/Server/Hostbase/templates/reversesoa.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/reversesoa.tmpl diff --git a/src/lib/Server/Hostbase/templates/zone.tmpl b/src/lib/Bcfg2/Server/Hostbase/templates/zone.tmpl index aad48d179..aad48d179 100644 --- a/src/lib/Server/Hostbase/templates/zone.tmpl +++ b/src/lib/Bcfg2/Server/Hostbase/templates/zone.tmpl diff --git a/src/lib/Server/Hostbase/test/harness.py b/src/lib/Bcfg2/Server/Hostbase/test/harness.py index befcff5c0..befcff5c0 100644 --- a/src/lib/Server/Hostbase/test/harness.py +++ b/src/lib/Bcfg2/Server/Hostbase/test/harness.py diff --git a/src/lib/Server/Hostbase/test/test_environ_settings.py b/src/lib/Bcfg2/Server/Hostbase/test/test_environ_settings.py index ad35c624e..ad35c624e 100644 --- a/src/lib/Server/Hostbase/test/test_environ_settings.py +++ b/src/lib/Bcfg2/Server/Hostbase/test/test_environ_settings.py diff --git a/src/lib/Server/Hostbase/test/test_ldapauth.py b/src/lib/Bcfg2/Server/Hostbase/test/test_ldapauth.py index 7fc009ad2..7fc009ad2 100644 --- a/src/lib/Server/Hostbase/test/test_ldapauth.py +++ b/src/lib/Bcfg2/Server/Hostbase/test/test_ldapauth.py diff --git a/src/lib/Server/Hostbase/test/test_settings.py b/src/lib/Bcfg2/Server/Hostbase/test/test_settings.py index 0dfc30f38..0dfc30f38 100644 --- a/src/lib/Server/Hostbase/test/test_settings.py +++ b/src/lib/Bcfg2/Server/Hostbase/test/test_settings.py diff --git a/src/lib/Server/Hostbase/urls.py b/src/lib/Bcfg2/Server/Hostbase/urls.py index 01fe97d4f..01fe97d4f 100644 --- a/src/lib/Server/Hostbase/urls.py +++ b/src/lib/Bcfg2/Server/Hostbase/urls.py diff --git a/src/lib/Server/Lint/Bundles.py b/src/lib/Bcfg2/Server/Lint/Bundles.py index 472915cfd..e6b6307f2 100644 --- a/src/lib/Server/Lint/Bundles.py +++ b/src/lib/Bcfg2/Server/Lint/Bundles.py @@ -1,9 +1,8 @@ import lxml.etree import Bcfg2.Server.Lint - + class Bundles(Bcfg2.Server.Lint.ServerPlugin): """ Perform various bundle checks """ - def Run(self): """ run plugin """ if 'Bundler' in self.core.plugins: @@ -15,6 +14,11 @@ class Bundles(Bcfg2.Server.Lint.ServerPlugin): Bcfg2.Server.Plugins.SGenshi.SGenshiTemplateFile): self.bundle_names(bundle) + @classmethod + def Errors(cls): + return {"bundle-not-found":"error", + "inconsistent-bundle-name":"warning"} + def missing_bundles(self): """ find bundles listed in Metadata but not implemented in Bundler """ if self.files is None: @@ -41,21 +45,10 @@ class Bundles(Bcfg2.Server.Lint.ServerPlugin): except AttributeError: # genshi template xdata = lxml.etree.parse(bundle.template.filepath).getroot() - + fname = bundle.name.split('Bundler/')[1].split('.')[0] bname = xdata.get('name') if fname != bname: self.LintError("inconsistent-bundle-name", "Inconsistent bundle name: filename is %s, bundle name is %s" % (fname, bname)) - - def sgenshi_groups(self, bundle): - """ ensure that Genshi Bundles do not include <Group> tags, - which are not supported """ - xdata = lxml.etree.parse(bundle.name) - groups = [self.RenderXML(g) - for g in xdata.getroottree().findall("//Group")] - if groups: - self.LintError("group-tag-not-allowed", - "<Group> tag is not allowed in SGenshi Bundle:\n%s" % - "\n".join(groups)) diff --git a/src/lib/Server/Lint/Comments.py b/src/lib/Bcfg2/Server/Lint/Comments.py index 19fae1b08..f5d0e265f 100644 --- a/src/lib/Server/Lint/Comments.py +++ b/src/lib/Bcfg2/Server/Lint/Comments.py @@ -1,6 +1,10 @@ import os.path import lxml.etree import Bcfg2.Server.Lint +from Bcfg2.Server.Plugins.Cfg.CfgPlaintextGenerator import CfgPlaintextGenerator +from Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator import CfgGenshiGenerator +from Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator import CfgCheetahGenerator +from Bcfg2.Server.Plugins.Cfg.CfgInfoXML import CfgInfoXML class Comments(Bcfg2.Server.Lint.ServerPlugin): """ check files for various required headers """ @@ -13,9 +17,15 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): self.check_properties() self.check_metadata() self.check_cfg() - self.check_infoxml() self.check_probes() + @classmethod + def Errors(cls): + return {"unexpanded-keywords":"warning", + "keywords-not-found":"warning", + "comments-not-found":"warning", + "broken-xinclude-chain":"warning"} + def required_keywords(self, rtype): """ given a file type, fetch the list of required VCS keywords from the bcfg2-lint config """ @@ -31,13 +41,13 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): fetch the list of required items from the bcfg2-lint config """ if itype not in self.config_cache: self.config_cache[itype] = {} - + if rtype not in self.config_cache[itype]: rv = [] global_item = "global_%ss" % itype if global_item in self.config: rv.extend(self.config[global_item].split(",")) - + item = "%s_%ss" % (rtype.lower(), itype) if item in self.config: if self.config[item]: @@ -83,25 +93,24 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): "metadata") def check_cfg(self): - """ check Cfg files for required headers """ + """ check Cfg files and info.xml files for required headers """ if 'Cfg' in self.core.plugins: for entryset in self.core.plugins['Cfg'].entries.values(): for entry in entryset.entries.values(): - if entry.name.endswith(".genshi"): + rtype = None + if isinstance(entry, CfgGenshiGenerator): rtype = "tgenshi" - else: + elif isinstance(entry, CfgPlaintextGenerator): rtype = "cfg" - self.check_plaintext(entry.name, entry.data, rtype) - - def check_infoxml(self): - """ check info.xml files for required headers """ - if 'Cfg' in self.core.plugins: - for entryset in self.core.plugins['Cfg'].entries.items(): - if (hasattr(entryset, "infoxml") and - entryset.infoxml is not None): - self.check_xml(entryset.infoxml.name, - entryset.infoxml.pnode.data, - "infoxml") + elif isinstance(entry, CfgCheetahGenerator): + rtype = "tcheetah" + elif isinstance(entry, CfgInfoXML): + self.check_xml(entry.infoxml.name, + entry.infoxml.pnode.data, + "infoxml") + continue + if rtype: + self.check_plaintext(entry.name, entry.data, rtype) def check_probes(self): """ check probes for required headers """ @@ -128,7 +137,7 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): # None == found but not expanded # True == found and expanded found = dict((k, False) for k in self.required_keywords(rtype)) - + for line in lines: # we check for both '$<keyword>:' and '$<keyword>$' to see # if the keyword just hasn't been expanded @@ -155,7 +164,7 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): # next, check for required comments. found is just # boolean found = dict((k, False) for k in self.required_comments(rtype)) - + for line in lines: for (comment, status) in found.items(): if not status: diff --git a/src/lib/Server/Lint/Duplicates.py b/src/lib/Bcfg2/Server/Lint/Duplicates.py index 75f620603..ee6b7a2e6 100644 --- a/src/lib/Server/Lint/Duplicates.py +++ b/src/lib/Bcfg2/Server/Lint/Duplicates.py @@ -22,6 +22,14 @@ class Duplicates(Bcfg2.Server.Lint.ServerPlugin): if self.clients_xdata is not None: self.duplicate_clients() + @classmethod + def Errors(cls): + return {"broken-xinclude-chain":"warning", + "duplicate-client":"error", + "duplicate-group":"error", + "duplicate-package":"error", + "multiple-default-groups":"error"} + def load_xdata(self): """ attempt to load XML data for groups and clients. only actually load data if all documents reference in XIncludes can @@ -30,7 +38,7 @@ class Duplicates(Bcfg2.Server.Lint.ServerPlugin): self.groups_xdata = self.metadata.clients_xml.xdata if self.has_all_xincludes("clients.xml"): self.clients_xdata = self.metadata.clients_xml.xdata - + def duplicate_groups(self): """ find duplicate groups """ self.duplicate_entries(self.clients_xdata.xpath('//Groups/Group'), diff --git a/src/lib/Server/Lint/Genshi.py b/src/lib/Bcfg2/Server/Lint/Genshi.py index 56803246c..b6007161e 100755 --- a/src/lib/Server/Lint/Genshi.py +++ b/src/lib/Bcfg2/Server/Lint/Genshi.py @@ -1,9 +1,8 @@ import genshi.template import Bcfg2.Server.Lint - + class Genshi(Bcfg2.Server.Lint.ServerPlugin): """ Check Genshi templates for syntax errors """ - def Run(self): """ run plugin """ loader = genshi.template.TemplateLoader() @@ -12,10 +11,14 @@ class Genshi(Bcfg2.Server.Lint.ServerPlugin): self.check_files(self.core.plugins[plugin].entries, loader=loader) + @classmethod + def Errors(cls): + return {"genshi-syntax-error":"error"} + def check_files(self, entries, loader=None): if loader is None: loader = genshi.template.TemplateLoader() - + for eset in entries.values(): for fname, sdata in list(eset.entries.items()): if (self.HandlesFile(fname) and @@ -23,6 +26,7 @@ class Genshi(Bcfg2.Server.Lint.ServerPlugin): try: loader.load(sdata.name, cls=genshi.template.NewTextTemplate) - except genshi.template.TemplateSyntaxError, err: + except genshi.template.TemplateSyntaxError: + err = sys.exc_info()[1] self.LintError("genshi-syntax-error", "Genshi syntax error: %s" % err) diff --git a/src/lib/Server/Lint/GroupPatterns.py b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py index b69d7a5d8..431ba4056 100644 --- a/src/lib/Server/Lint/GroupPatterns.py +++ b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py @@ -1,7 +1,7 @@ import sys import Bcfg2.Server.Lint from Bcfg2.Server.Plugins.GroupPatterns import PatternMap - + class GroupPatterns(Bcfg2.Server.Lint.ServerPlugin): """ Check Genshi templates for syntax errors """ @@ -14,12 +14,16 @@ class GroupPatterns(Bcfg2.Server.Lint.ServerPlugin): self.check(entry, groups, ptype='NamePattern') self.check(entry, groups, ptype='NameRange') + @classmethod + def Errors(cls): + return {"pattern-fails-to-initialize":"error"} + def check(self, entry, groups, ptype="NamePattern"): if ptype == "NamePattern": pmap = lambda p: PatternMap(p, None, groups) else: pmap = lambda p: PatternMap(None, p, groups) - + for el in entry.findall(ptype): pat = el.text try: diff --git a/src/lib/Server/Lint/InfoXML.py b/src/lib/Bcfg2/Server/Lint/InfoXML.py index 2054e23bf..db6aeea73 100644 --- a/src/lib/Server/Lint/InfoXML.py +++ b/src/lib/Bcfg2/Server/Lint/InfoXML.py @@ -1,23 +1,32 @@ import os.path import Bcfg2.Options import Bcfg2.Server.Lint +from Bcfg2.Server.Plugins.Cfg.CfgInfoXML import CfgInfoXML class InfoXML(Bcfg2.Server.Lint.ServerPlugin): """ ensure that all config files have an info.xml file""" - def Run(self): if 'Cfg' in self.core.plugins: for filename, entryset in self.core.plugins['Cfg'].entries.items(): infoxml_fname = os.path.join(entryset.path, "info.xml") if self.HandlesFile(infoxml_fname): - if (hasattr(entryset, "infoxml") and - entryset.infoxml is not None): - self.check_infoxml(infoxml_fname, - entryset.infoxml.pnode.data) - else: + found = False + for entry in entryset.entries.values(): + if isinstance(entry, CfgInfoXML): + self.check_infoxml(infoxml_fname, + entry.infoxml.pnode.data) + found = True + if not found: self.LintError("no-infoxml", "No info.xml found for %s" % filename) + @classmethod + def Errors(cls): + return {"no-infoxml":"warning", + "paranoid-false":"warning", + "broken-xinclude-chain":"warning", + "required-infoxml-attrs-missing":"error"} + def check_infoxml(self, fname, xdata): for info in xdata.getroottree().findall("//Info"): required = [] diff --git a/src/lib/Server/Lint/MergeFiles.py b/src/lib/Bcfg2/Server/Lint/MergeFiles.py index ff6e3449a..68d010316 100644 --- a/src/lib/Server/Lint/MergeFiles.py +++ b/src/lib/Bcfg2/Server/Lint/MergeFiles.py @@ -2,20 +2,28 @@ import os import copy from difflib import SequenceMatcher import Bcfg2.Server.Lint +from Bcfg2.Server.Plugins.Cfg import CfgGenerator class MergeFiles(Bcfg2.Server.Lint.ServerPlugin): """ find Probes or Cfg files with multiple similar files that might be merged into one """ - def Run(self): if 'Cfg' in self.core.plugins: self.check_cfg() if 'Probes' in self.core.plugins: self.check_probes() + @classmethod + def Errors(cls): + return {"merge-cfg":"warning", + "merge-probes":"warning"} + + def check_cfg(self): for filename, entryset in self.core.plugins['Cfg'].entries.items(): - for mset in self.get_similar(entryset.entries): + candidates = dict([(f, e) for f, e in entryset.entries.items() + if isinstance(e, CfgGenerator)]) + for mset in self.get_similar(candidates): self.LintError("merge-cfg", "The following files are similar: %s. " "Consider merging them into a single Genshi " @@ -26,7 +34,7 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin): def check_probes(self): probes = self.core.plugins['Probes'].probes.entries for mset in self.get_similar(probes): - self.LintError("merge-cfg", + self.LintError("merge-probes", "The following probes are similar: %s. " "Consider merging them into a single probe." % ", ".join([p for p in mset])) @@ -66,4 +74,4 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin): threshold)) return rv - + diff --git a/src/lib/Server/Lint/Pkgmgr.py b/src/lib/Bcfg2/Server/Lint/Pkgmgr.py index 8f099163a..ceb46238a 100644 --- a/src/lib/Server/Lint/Pkgmgr.py +++ b/src/lib/Bcfg2/Server/Lint/Pkgmgr.py @@ -4,7 +4,6 @@ import Bcfg2.Server.Lint class Pkgmgr(Bcfg2.Server.Lint.ServerlessPlugin): """ find duplicate Pkgmgr entries with the same priority """ - def Run(self): pset = set() for pfile in glob.glob("%s/Pkgmgr/*.xml" % self.config['repo']): @@ -33,3 +32,7 @@ class Pkgmgr(Bcfg2.Server.Lint.ServerlessPlugin): (pkg.get('name'), priority, ptype)) else: pset.add(ptuple) + + @classmethod + def Errors(cls): + return {"duplicate-packages":"error"} diff --git a/src/lib/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py index 55206d2ba..6f76cf2db 100644 --- a/src/lib/Server/Lint/RequiredAttrs.py +++ b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py @@ -6,7 +6,6 @@ from Bcfg2.Server.Plugins.Packages import Apt, Yum class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): """ verify attributes for configuration entries (as defined in doc/server/configurationentries) """ - def __init__(self, *args, **kwargs): Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs) self.required_attrs = { @@ -38,6 +37,14 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): self.check_rules() self.check_bundles() + @classmethod + def Errors(cls): + return {"unknown-entry-type":"error", + "unknown-entry-tag":"error", + "required-attrs-missing":"error", + "extra-attrs":"warning"} + + def check_packages(self): """ check package sources for Source entries with missing attrs """ if 'Packages' in self.core.plugins: diff --git a/src/lib/Bcfg2/Server/Lint/TemplateHelper.py b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py new file mode 100644 index 000000000..be270a59c --- /dev/null +++ b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py @@ -0,0 +1,64 @@ +import sys +import imp +import glob +import Bcfg2.Server.Lint +from Bcfg2.Server.Plugins.TemplateHelper import HelperModule + +class TemplateHelper(Bcfg2.Server.Lint.ServerlessPlugin): + """ find duplicate Pkgmgr entries with the same priority """ + def __init__(self, *args, **kwargs): + Bcfg2.Server.Lint.ServerlessPlugin.__init__(self, *args, **kwargs) + hm = HelperModule("foo.py", None, None) + self.reserved_keywords = dir(hm) + + def Run(self): + for helper in glob.glob("%s/TemplateHelper/*.py" % self.config['repo']): + if not self.HandlesFile(helper): + continue + + match = HelperModule._module_name_re.search(helper) + if match: + module_name = match.group(1) + else: + module_name = helper + + try: + module = imp.load_source(module_name, helper) + except: + err = sys.exc_info()[1] + self.LintError("templatehelper-import-error", + "Failed to import %s: %s" % + (helper, err)) + continue + + if not hasattr(module, "__export__"): + self.LintError("templatehelper-no-export", + "%s has no __export__ list" % helper) + continue + elif not isinstance(module.__export__, list): + self.LintError("templatehelper-nonlist-export", + "__export__ is not a list in %s" % helper) + continue + + for sym in module.__export__: + if not hasattr(module, sym): + self.LintError("templatehelper-nonexistent-export", + "%s: exported symbol %s does not exist" % + (helper, sym)) + elif sym in self.reserved_keywords: + self.LintError("templatehelper-reserved-export", + "%s: exported symbol %s is reserved" % + (helper, sym)) + elif sym.startswith("_"): + self.LintError("templatehelper-underscore-export", + "%s: exported symbol %s starts with underscore" % + (helper, sym)) + + @classmethod + def Errors(cls): + return {"templatehelper-import-error":"error", + "templatehelper-no-export":"error", + "templatehelper-nonlist-export":"error", + "templatehelper-nonexistent-export":"error", + "templatehelper-reserved-export":"error", + "templatehelper-underscore-export":"warning"} diff --git a/src/lib/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py index 952a65365..05fedc313 100644 --- a/src/lib/Server/Lint/Validate.py +++ b/src/lib/Bcfg2/Server/Lint/Validate.py @@ -36,7 +36,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): def Run(self): schemadir = self.config['schema'] - + for path, schemaname in self.filesets.items(): try: filelist = self.filelists[path] @@ -63,6 +63,16 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): self.check_properties() + @classmethod + def Errors(cls): + return {"broken-xinclude-chain":"warning", + "schema-failed-to-parse":"warning", + "properties-schema-not-found":"warning", + "xml-failed-to-parse":"error", + "xml-failed-to-read":"error", + "xml-failed-to-verify":"error", + "input-output-error":"error"} + def check_properties(self): """ check Properties files against their schemas """ for filename in self.filelists['props']: @@ -98,7 +108,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): self.LintError("xml-failed-to-read", "Failed to open file %s" % filename) return False - + if not schema.validate(datafile): cmd = ["xmllint"] if self.files is None: @@ -180,7 +190,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): included = set([ent.get('href') for ent in xdata.findall('./{http://www.w3.org/2001/XInclude}include')]) rv = [] - + while included: try: filename = included.pop() diff --git a/src/lib/Server/Lint/__init__.py b/src/lib/Bcfg2/Server/Lint/__init__.py index 4d6df8c8f..5d7dd707b 100644 --- a/src/lib/Server/Lint/__init__.py +++ b/src/lib/Bcfg2/Server/Lint/__init__.py @@ -1,5 +1,3 @@ -__revision__ = '$Revision$' - __all__ = ['Bundles', 'Comments', 'Duplicates', @@ -56,14 +54,20 @@ class Plugin (object): self.config = config self.logger = logging.getLogger('bcfg2-lint') if errorhandler is None: - self.errorHandler = ErrorHandler() + self.errorhandler = ErrorHandler() else: - self.errorHandler = errorhandler + self.errorhandler = errorhandler + self.errorhandler.RegisterErrors(self.Errors()) def Run(self): """ run the plugin. must be overloaded by child classes """ pass + @classmethod + def Errors(cls): + """ returns a dict of errors the plugin supplies. must be + overloaded by child classes """ + def HandlesFile(self, fname): """ returns true if the given file should be handled by the plugin according to the files list, false otherwise """ @@ -75,8 +79,8 @@ class Plugin (object): fname)) in self.files) def LintError(self, err, msg): - self.errorHandler.dispatch(err, msg) - + self.errorhandler.dispatch(err, msg) + def RenderXML(self, element): """render an XML element for error output -- line number prefixed, no children""" @@ -93,37 +97,6 @@ class Plugin (object): class ErrorHandler (object): - # how to handle different errors by default - _errors = {"no-infoxml":"warning", - "paranoid-false":"warning", - "bundle-not-found":"error", - "inconsistent-bundle-name":"warning", - "group-tag-not-allowed":"error", - "unexpanded-keywords":"warning", - "keywords-not-found":"warning", - "comments-not-found":"warning", - "broken-xinclude-chain":"warning", - "duplicate-client":"error", - "duplicate-group":"error", - "duplicate-package":"error", - "multiple-default-groups":"error", - "required-infoxml-attrs-missing":"error", - "unknown-entry-type":"error", - "required-attrs-missing":"error", - "extra-attrs":"warning", - "schema-failed-to-parse":"warning", - "properties-schema-not-found":"warning", - "xml-failed-to-parse":"error", - "xml-failed-to-read":"error", - "xml-failed-to-verify":"error", - "merge-cfg":"warning", - "merge-probes":"warning", - "input-output-error":"error", - "genshi-syntax-error":"error", - "pattern-fails-to-initialize":"error", - "cat-file-used":"warning", - "diff-file-used":"warning"} - def __init__(self, config=None): self.errors = 0 self.warnings = 0 @@ -149,7 +122,8 @@ class ErrorHandler (object): else: self._handlers[err] = self.debug - for err, action in self._errors.items(): + def RegisterErrors(self, errors): + for err, action in errors.items(): if err not in self._handlers: if "warn" in action: self._handlers[err] = self.warn @@ -157,7 +131,7 @@ class ErrorHandler (object): self._handlers[err] = self.error else: self._handlers[err] = self.debug - + def dispatch(self, err, msg): if err in self._handlers: self._handlers[err](msg) diff --git a/src/lib/Server/Plugin.py b/src/lib/Bcfg2/Server/Plugin.py index 9b3c5814f..ca37431a2 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Bcfg2/Server/Plugin.py @@ -1,5 +1,4 @@ """This module provides the baseclass for Bcfg2 Server Plugins.""" -__revision__ = '$Revision$' import copy import logging @@ -71,7 +70,7 @@ class Debuggable(object): self.__class__.__name__) self.debug_flag = False self.logger = logging.getLogger(name) - + def toggle_debug(self): self.debug_flag = not self.debug_flag @@ -84,7 +83,6 @@ class Plugin(Debuggable): """This is the base class for all Bcfg2 Server plugins. Several attributes must be defined in the subclass: name : the name of the plugin - __version__ : a version string __author__ : the author/contact for the plugin Plugins can provide three basic types of functionality: @@ -93,7 +91,6 @@ class Plugin(Debuggable): - Data collection (overloading GetProbes/ReceiveData) """ name = 'Plugin' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = False deprecated = False @@ -106,14 +103,14 @@ class Plugin(Debuggable): def __init__(self, core, datastore): """Initialize the plugin. - + :param core: the Bcfg2.Server.Core initializing the plugin :param datastore: the filesystem path of Bcfg2's repository """ object.__init__(self) self.Entries = {} self.core = core - self.data = "%s/%s" % (datastore, self.name) + self.data = os.path.join(datastore, self.name) self.running = True Debuggable.__init__(self, name=self.name) @@ -125,6 +122,9 @@ class Plugin(Debuggable): def shutdown(self): self.running = False + def __str__(self): + return "%s Plugin" % self.__class__.__name__ + class Generator(object): """Generator plugins contribute to literal client configurations.""" @@ -365,7 +365,7 @@ class FileBacked(object): object.__init__(self) self.data = '' self.name = name - + def HandleEvent(self, event=None): """Read file upon update.""" if event and event.code2str() not in ['exists', 'changed', 'created']: @@ -400,7 +400,7 @@ class DirectoryBacked(object): :param data: the path to the data directory that will be monitored. :param fam: The FileMonitor object used to receive - notifications of changes. + notifications of changes. """ object.__init__(self) @@ -458,7 +458,7 @@ class DirectoryBacked(object): def HandleEvent(self, event): """Handle FAM/Gamin events. - + This method is invoked by FAM/Gamin when it detects a change to a filesystem object we have requsted to be monitored. @@ -594,6 +594,29 @@ class SingleXMLFileBacked(XMLFileBacked): self.fam = fam self.fam.AddMonitor(filename, self) + def _follow_xincludes(self, fname=None, xdata=None): + ''' follow xincludes, adding included files to fam and to + self.extras ''' + if xdata is None: + if fname is None: + xdata = self.xdata.getroottree() + else: + xdata = lxml.etree.parse(fname) + included = [ent.get('href') + for ent in xdata.findall('//{http://www.w3.org/2001/XInclude}include')] + for name in included: + if name not in self.extras: + if name.startswith("/"): + fpath = name + else: + fpath = os.path.join(os.path.dirname(self.name), name) + self.add_monitor(fpath, name) + self._follow_xincludes(fname=fpath) + + def add_monitor(self, fpath, fname): + self.fam.AddMonitor(fpath, self) + self.extras.append(fname) + def Index(self): """Build local data structures.""" try: @@ -603,22 +626,14 @@ class SingleXMLFileBacked(XMLFileBacked): logger.error("Failed to parse %s: %s" % (self.name, err)) raise Bcfg2.Server.Plugin.PluginInitError - included = [ent.get('href') - for ent in self.xdata.findall('./{http://www.w3.org/2001/XInclude}include')] - if included: - for name in included: - if name not in self.extras: - self.fam.AddMonitor(os.path.join(os.path.dirname(self.name), - name), - self) - self.extras.append(name) + self._follow_xincludes() + if self.extras: try: self.xdata.getroottree().xinclude() except lxml.etree.XIncludeError: err = sys.exc_info()[1] logger.error("XInclude failed on %s: %s" % (self.name, err)) - self.entries = self.xdata.getchildren() if self.__identifier__ is not None: self.label = self.xdata.attrib[self.__identifier__] @@ -627,7 +642,7 @@ class SingleXMLFileBacked(XMLFileBacked): class StructFile(XMLFileBacked): """This file contains a set of structure file formatting logic.""" __identifier__ = None - + def __init__(self, name): XMLFileBacked.__init__(self, name) @@ -660,7 +675,7 @@ class StructFile(XMLFileBacked): for child in item.iterchildren(): rv.extend(self._match(child, metadata)) return [rv] - + def Match(self, metadata): """Return matching fragments of independent.""" rv = [] @@ -792,7 +807,7 @@ class XMLSrc(XMLFileBacked): return str(self.items) -class InfoXML (XMLSrc): +class InfoXML(XMLSrc): __node__ = InfoNode @@ -834,7 +849,7 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): attrs = self.get_attrs(entry, metadata) for key, val in list(attrs.items()): entry.attrib[key] = val - + def get_attrs(self, entry, metadata): """ get a list of attributes to add to the entry during the bind """ for src in self.entries.values(): @@ -933,15 +948,18 @@ class SpecificData(object): return try: self.data = open(self.name).read() + except UnicodeDecodeError: + self.data = open(self.name, mode='rb').read() except: logger.error("Failed to read file %s" % self.name) -class EntrySet: +class EntrySet(Debuggable): """Entry sets deal with the host- and group-specific entries.""" ignore = re.compile("^(\.#.*|.*~|\\..*\\.(sw[px])|.*\\.genshi_include)$") def __init__(self, basename, path, entry_type, encoding): + Debuggable.__init__(self, name=basename) self.path = path self.entry_type = entry_type self.entries = {} @@ -952,14 +970,22 @@ class EntrySet: pattern += '(G(?P<prio>\d+)_(?P<group>\S+))))?$' self.specific = re.compile(pattern) + def debug_log(self, message, flag=None): + if (flag is None and self.debug_flag) or flag: + logger.error(message) + + def sort_by_specific(self, one, other): + return cmp(one.specific, other.specific) + def get_matching(self, metadata): return [item for item in list(self.entries.values()) if item.specific.matches(metadata)] - def best_matching(self, metadata): + def best_matching(self, metadata, matching=None): """ Return the appropriate interpreted template from the set of available templates. """ - matching = self.get_matching(metadata) + if matching is None: + matching = self.get_matching(metadata) hspec = [ent for ent in matching if ent.specific.hostname] if hspec: @@ -1003,25 +1029,32 @@ class EntrySet: elif action == 'deleted': del self.entries[event.filename] - def entry_init(self, event): + def entry_init(self, event, entry_type=None, specific=None): """Handle template and info file creation.""" + if entry_type is None: + entry_type = self.entry_type + if event.filename in self.entries: logger.warn("Got duplicate add for %s" % event.filename) else: - fpath = "%s/%s" % (self.path, event.filename) + fpath = os.path.join(self.path, event.filename) try: - spec = self.specificity_from_filename(event.filename) + spec = self.specificity_from_filename(event.filename, + specific=specific) except SpecificityError: if not self.ignore.match(event.filename): - logger.error("Could not process filename %s; ignoring" % fpath) + logger.error("Could not process filename %s; ignoring" % + fpath) return - self.entries[event.filename] = self.entry_type(fpath, - spec, self.encoding) + self.entries[event.filename] = entry_type(fpath, spec, + self.encoding) self.entries[event.filename].handle_event(event) - def specificity_from_filename(self, fname): + def specificity_from_filename(self, fname, specific=None): """Construct a specificity instance from a filename and regex.""" - data = self.specific.match(fname) + if specific is None: + specific = self.specific + data = specific.match(fname) if not data: raise SpecificityError(fname) kwargs = {} @@ -1038,7 +1071,7 @@ class EntrySet: def update_metadata(self, event): """Process info and info.xml files for the templates.""" - fpath = "%s/%s" % (self.path, event.filename) + fpath = os.path.join(self.path, event.filename) if event.filename == 'info.xml': if not self.infoxml: self.infoxml = InfoXML(fpath, True) @@ -1093,7 +1126,6 @@ class EntrySet: class GroupSpool(Plugin, Generator): """Unified interface for handling group-specific data (e.g. .G## files).""" name = 'GroupSpool' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filename_pattern = "" es_child_cls = object @@ -1179,65 +1211,3 @@ class GroupSpool(Plugin, Generator): return reqid = self.core.fam.AddMonitor(name, self) self.handles[reqid] = relative - -class SimpleConfig(FileBacked, - ConfigParser.SafeConfigParser): - ''' a simple plugin config using ConfigParser ''' - _required = True - - def __init__(self, plugin): - filename = os.path.join(plugin.data, plugin.name.lower() + ".conf") - self.plugin = plugin - self.fam = self.plugin.core.fam - self.read_files = set() - Bcfg2.Server.Plugin.FileBacked.__init__(self, filename) - ConfigParser.SafeConfigParser.__init__(self) - - if (self._required or - (not self._required and os.path.exists(self.name))): - self.fam.AddMonitor(self.name, self) - - def Index(self): - """ Build local data structures """ - for section in self.sections(): - self.remove_section(section) - self.read_files.update(self.read(self.name)) - - def get(self, section, option, **kwargs): - """ convenience method for getting config items """ - default = None - if 'default' in kwargs: - default = kwargs['default'] - del kwargs['default'] - try: - return ConfigParser.SafeConfigParser.get(self, section, option, - **kwargs) - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): - if default is not None: - return default - else: - raise - - def getboolean(self, section, option, **kwargs): - """ convenience method for getting boolean config items """ - default = None - if 'default' in kwargs: - default = kwargs['default'] - del kwargs['default'] - try: - return ConfigParser.SafeConfigParser.getboolean(self, section, - option, **kwargs) - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, - ValueError): - if default is not None: - return default - else: - raise - - @property - def loaded(self): - if os.path.exists(self.name): - return self.name in self.read_files - else: - return True - diff --git a/src/lib/Server/Plugins/Account.py b/src/lib/Bcfg2/Server/Plugins/Account.py index f67819b9d..f2703dccb 100644 --- a/src/lib/Server/Plugins/Account.py +++ b/src/lib/Bcfg2/Server/Plugins/Account.py @@ -1,5 +1,4 @@ """This handles authentication setup.""" -__revision__ = '$Revision$' import Bcfg2.Server.Plugin @@ -16,7 +15,6 @@ class Account(Bcfg2.Server.Plugin.Plugin, """ name = 'Account' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/BB.py b/src/lib/Bcfg2/Server/Plugins/BB.py index 0df6be4e8..bd518ad19 100644 --- a/src/lib/Server/Plugins/BB.py +++ b/src/lib/Bcfg2/Server/Plugins/BB.py @@ -64,7 +64,6 @@ class BB(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): """The BB plugin maps users to machines and metadata to machines.""" name = 'BB' - version = '$Revision$' deprecated = True def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Base.py b/src/lib/Bcfg2/Server/Plugins/Base.py index e8d798ae4..389ca7a95 100644 --- a/src/lib/Server/Plugins/Base.py +++ b/src/lib/Bcfg2/Server/Plugins/Base.py @@ -1,5 +1,4 @@ """This module sets up a base list of configuration entries.""" -__revision__ = '$Revision$' import copy import lxml.etree @@ -18,7 +17,6 @@ class Base(Bcfg2.Server.Plugin.Plugin, needed for most actual systems. """ name = 'Base' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = Bcfg2.Server.Plugin.StructFile deprecated = True diff --git a/src/lib/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py index c795d7f90..cbc452608 100644 --- a/src/lib/Server/Plugins/Bundler.py +++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py @@ -1,5 +1,4 @@ """This provides bundle clauses with translation functionality.""" -__revision__ = '$Revision$' import copy import lxml.etree @@ -35,7 +34,6 @@ class Bundler(Bcfg2.Server.Plugin.Plugin, bundle/translation scheme from Bcfg1. """ name = 'Bundler' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' patterns = re.compile('^(?P<name>.*)\.(xml|genshi)$') diff --git a/src/lib/Server/Plugins/Bzr.py b/src/lib/Bcfg2/Server/Plugins/Bzr.py index a9a5eb814..a71021cb5 100644 --- a/src/lib/Server/Plugins/Bzr.py +++ b/src/lib/Bcfg2/Server/Plugins/Bzr.py @@ -10,7 +10,6 @@ class Bzr(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Bzr is a version plugin for dealing with Bcfg2 repos.""" name = 'Bzr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCatFilter.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCatFilter.py new file mode 100644 index 000000000..f6b175832 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCatFilter.py @@ -0,0 +1,20 @@ +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgFilter + +logger = logging.getLogger(__name__) + +class CfgCatFilter(CfgFilter): + __extensions__ = ['cat'] + + def modify_data(self, entry, metadata, data): + datalines = data.strip().split('\n') + for line in self.data.split('\n'): + if not line: + continue + if line.startswith('+'): + datalines.append(line[1:]) + elif line.startswith('-'): + if line[1:] in datalines: + datalines.remove(line[1:]) + return "\n".join(datalines) + "\n" diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py new file mode 100644 index 000000000..3edd1d8cb --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py @@ -0,0 +1,33 @@ +import copy +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgGenerator + +logger = logging.getLogger(__name__) + +try: + import Cheetah.Template + import Cheetah.Parser + have_cheetah = True +except ImportError: + have_cheetah = False + + +class CfgCheetahGenerator(CfgGenerator): + __extensions__ = ['cheetah'] + settings = dict(useStackFrames=False) + + def __init__(self, fname, spec, encoding): + CfgGenerator.__init__(self, fname, spec, encoding) + if not have_cheetah: + msg = "Cfg: Cheetah is not available: %s" % entry.get("name") + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + def get_data(self, entry, metadata): + template = Cheetah.Template.Template(self.data, + compilerSettings=self.settings) + template.metadata = metadata + template.path = entry.get('realname', entry.get('name')) + template.source_path = self.path + return template.respond() diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgDiffFilter.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgDiffFilter.py new file mode 100644 index 000000000..906666c21 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgDiffFilter.py @@ -0,0 +1,27 @@ +import os +import logging +import tempfile +import Bcfg2.Server.Plugin +from subprocess import Popen, PIPE +from Bcfg2.Server.Plugins.Cfg import CfgFilter + +logger = logging.getLogger(__name__) + +class CfgDiffFilter(CfgFilter): + __extensions__ = ['diff'] + + def modify_data(self, entry, metadata, data): + basehandle, basename = tempfile.mkstemp() + open(basename, 'w').write(data) + os.close(basehandle) + + cmd = ["patch", "-u", "-f", basefile.name] + patch = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + stderr = patch.communicate(input=self.data)[1] + ret = patch.wait() + output = open(basefile.name, 'r').read() + os.unlink(basefile.name) + if ret != 0: + logger.error("Error applying diff %s: %s" % (delta.name, stderr)) + raise Bcfg2.Server.Plugin.PluginExecutionError('delta', delta) + return output diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgExternalCommandVerifier.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgExternalCommandVerifier.py new file mode 100644 index 000000000..f0c1109ec --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgExternalCommandVerifier.py @@ -0,0 +1,33 @@ +import os +import shlex +import logging +import Bcfg2.Server.Plugin +from subprocess import Popen, PIPE +from Bcfg2.Server.Plugins.Cfg import CfgVerifier, CfgVerificationError + +logger = logging.getLogger(__name__) + +class CfgExternalCommandVerifier(CfgVerifier): + __basenames__ = [':test'] + + def verify_entry(self, entry, metadata, data): + proc = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + err = proc.communicate(input=data)[1] + rv = proc.wait() + if rv != 0: + raise CfgVerificationError(err) + + def handle_event(self, event): + if event.code2str() == 'deleted': + return + self.cmd = [] + if not os.access(self.name, os.X_OK): + bangpath = open(self.name).readline().strip() + if bangpath.startswith("#!"): + self.cmd.extend(shlex.split(bangpath[2:].strip())) + else: + msg = "Cannot execute %s" % self.name + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + self.cmd.append(self.name) + diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py new file mode 100644 index 000000000..2c0a076d7 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py @@ -0,0 +1,64 @@ +import sys +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgGenerator + +logger = logging.getLogger(__name__) + +try: + import genshi.core + from genshi.template import TemplateLoader, NewTextTemplate + have_genshi = True +except ImportError: + have_genshi = False + +# snipped from TGenshi +def removecomment(stream): + """A genshi filter that removes comments from the stream.""" + for kind, data, pos in stream: + if kind is genshi.core.COMMENT: + continue + yield kind, data, pos + + +class CfgGenshiGenerator(CfgGenerator): + __extensions__ = ['genshi'] + + def __init__(self, fname, spec, encoding): + CfgGenerator.__init__(self, fname, spec, encoding) + self.loader = TemplateLoader() + if not have_genshi: + msg = "Cfg: Genshi is not available: %s" % entry.get("name") + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + @classmethod + def ignore(cls, event, basename=None): + return (event.filename.endswith(".genshi_include") or + CfgGenerator.ignore(event, basename=basename)) + + def get_data(self, entry, metadata): + fname = entry.get('realname', entry.get('name')) + stream = \ + self.template.generate(name=fname, + metadata=metadata, + path=self.name).filter(removecomment) + try: + return stream.render('text', encoding=self.encoding, + strip_whitespace=False) + except TypeError: + return stream.render('text', encoding=self.encoding) + + def handle_event(self, event): + if event.code2str() == 'deleted': + return + CfgGenerator.handle_event(self, event) + try: + self.template = self.loader.load(self.name, cls=NewTextTemplate, + encoding=self.encoding) + except Exception: + msg = "Cfg: Could not load template %s: %s" % (self.name, + sys.exc_info()[1]) + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py new file mode 100644 index 000000000..8e962efb4 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py @@ -0,0 +1,24 @@ +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgInfo + +logger = logging.getLogger(__name__) + +class CfgInfoXML(CfgInfo): + __basenames__ = ['info.xml'] + + def __init__(self, path): + CfgInfo.__init__(self, path) + self.infoxml = Bcfg2.Server.Plugin.InfoXML(path, noprio=True) + + def bind_info_to_entry(self, entry, metadata): + mdata = dict() + self.infoxml.pnode.Match(metadata, mdata, entry=entry) + if 'Info' not in mdata: + logger.error("Failed to set metadata for file %s" % + entry.get('name')) + raise PluginExecutionError + self._set_info(entry, mdata['Info'][None]) + + def handle_event(self, event): + self.infoxml.HandleEvent() diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py new file mode 100644 index 000000000..54c17c6c5 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py @@ -0,0 +1,28 @@ +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgInfo + +logger = logging.getLogger(__name__) + +class CfgLegacyInfo(CfgInfo): + __basenames__ = ['info', ':info'] + + def bind_info_to_entry(self, entry, metadata): + self._set_info(entry, self.metadata) + + def handle_event(self, event): + if event.code2str() == 'deleted': + return + for line in open(self.path).readlines(): + match = Bcfg2.Server.Plugin.info_regex.match(line) + if not match: + logger.warning("Failed to parse line in %s: %s" % (fpath, line)) + continue + else: + self.metadata = \ + dict([(key, value) + for key, value in list(match.groupdict().items()) + if value]) + if ('perms' in self.metadata and + len(self.metadata['perms']) == 3): + self.metadata['perms'] = "0%s" % self.metadata['perms'] diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPlaintextGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPlaintextGenerator.py new file mode 100644 index 000000000..8e9aab465 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPlaintextGenerator.py @@ -0,0 +1,8 @@ +import logging +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Cfg import CfgGenerator + +logger = logging.getLogger(__name__) + +class CfgPlaintextGenerator(CfgGenerator): + pass diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py new file mode 100644 index 000000000..5d55f3cbe --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py @@ -0,0 +1,398 @@ +"""This module implements a config file repository.""" + +import re +import os +import sys +import stat +import pkgutil +import logging +import binascii +import lxml.etree +import Bcfg2.Options +import Bcfg2.Server.Plugin +from Bcfg2.Bcfg2Py3k import u_str + +logger = logging.getLogger(__name__) + +PROCESSORS = None +SETUP = None + +class CfgBaseFileMatcher(Bcfg2.Server.Plugin.SpecificData): + __basenames__ = [] + __extensions__ = [] + __ignore__ = [] + __specific__ = True + + def __init__(self, fname, spec, encoding): + Bcfg2.Server.Plugin.SpecificData.__init__(self, fname, spec, encoding) + self.encoding = encoding + self.regex = self.__class__.get_regex(fname) + + @classmethod + def get_regex(cls, fname=None, extensions=None): + if extensions is None: + extensions = cls.__extensions__ + if cls.__basenames__: + fname = '|'.join(cls.__basenames__) + + components = ['^(?P<basename>%s)' % fname] + if cls.__specific__: + components.append('(|\\.H_(?P<hostname>\S+?)|.G(?P<prio>\d+)_(?P<group>\S+?))') + if extensions: + components.append('\\.(?P<extension>%s)' % '|'.join(extensions)) + components.append('$') + return re.compile("".join(components)) + + @classmethod + def handles(cls, event, basename=None): + if cls.__basenames__: + basenames = cls.__basenames__ + else: + basenames = [basename] + + # do simple non-regex matching first + match = False + for bname in basenames: + if event.filename.startswith(os.path.basename(bname)): + match = True + break + return (match and + cls.get_regex(fname=os.path.basename(basename)).match(event.filename)) + + @classmethod + def ignore(cls, event, basename=None): + if not cls.__ignore__: + return False + + if cls.__basenames__: + basenames = cls.__basenames__ + else: + basenames = [basename] + + # do simple non-regex matching first + match = False + for bname in basenames: + if event.filename.startswith(os.path.basename(bname)): + match = True + break + return (match and + cls.get_regex(fname=os.path.basename(basename), + extensions=cls.__ignore__).match(event.filename)) + + + def __str__(self): + return "%s(%s)" % (self.__class__.__name__, self.name) + + def match(self, fname): + return self.regex.match(fname) + + +class CfgGenerator(CfgBaseFileMatcher): + """ CfgGenerators generate the initial content of a file """ + def get_data(self, entry, metadata): + return self.data + + +class CfgFilter(CfgBaseFileMatcher): + """ CfgFilters modify the initial content of a file after it's + been generated """ + def modify_data(self, entry, metadata, data): + raise NotImplementedError + + +class CfgInfo(CfgBaseFileMatcher): + """ CfgInfos provide metadata (owner, group, paranoid, etc.) for a + file entry """ + __specific__ = False + + def __init__(self, fname): + CfgBaseFileMatcher.__init__(self, fname, None, None) + + def bind_info_to_entry(self, entry, metadata): + raise NotImplementedError + + def _set_info(self, entry, info): + for key, value in list(info.items()): + entry.attrib.__setitem__(key, value) + + +class CfgVerifier(CfgBaseFileMatcher): + """ Verifiers validate entries """ + def verify_entry(self, entry, metadata, data): + raise NotImplementedError + + +class CfgVerificationError(Exception): + pass + + +class CfgDefaultInfo(CfgInfo): + def __init__(self, defaults): + CfgInfo.__init__(self, '') + self.defaults = defaults + + def bind_info_to_entry(self, entry, metadata): + self._set_info(entry, self.defaults) + +DEFAULT_INFO = CfgDefaultInfo(Bcfg2.Server.Plugin.default_file_metadata) + +class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): + def __init__(self, basename, path, entry_type, encoding): + Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, + entry_type, encoding) + self.specific = None + self.load_processors() + + def load_processors(self): + """ load Cfg file processors. this must be done at run-time, + not at compile-time, or we get a circular import and things + don't work. but finding the right way to do this at runtime + was ... problematic. so here it is, writing to a global + variable. Sorry 'bout that. """ + global PROCESSORS + if PROCESSORS is None: + PROCESSORS = [] + for submodule in pkgutil.walk_packages(path=__path__): + module = getattr(__import__("%s.%s" % + (__name__, + submodule[1])).Server.Plugins.Cfg, + submodule[1]) + proc = getattr(module, submodule[1]) + if set(proc.__mro__).intersection([CfgInfo, CfgFilter, + CfgGenerator, CfgVerifier]): + PROCESSORS.append(proc) + + def handle_event(self, event): + action = event.code2str() + + if event.filename not in self.entries: + if action not in ['exists', 'created', 'changed']: + # process a bogus changed event like a created + return + + for proc in PROCESSORS: + if proc.handles(event, basename=self.path): + if action == 'changed': + # warn about a bogus 'changed' event, but + # handle it like a 'created' + logger.warning("Got %s event for unknown file %s" % + (action, event.filename)) + self.debug_log("%s handling %s event on %s" % + (proc.__name__, action, event.filename)) + self.entry_init(event, proc) + return + elif proc.ignore(event, basename=self.path): + return + elif action == 'changed': + self.entries[event.filename].handle_event(event) + elif action == 'deleted': + del self.entries[event.filename] + return + + logger.error("Could not process event %s for %s; ignoring" % + (action, event.filename)) + + def entry_init(self, event, proc): + if proc.__specific__: + Bcfg2.Server.Plugin.EntrySet.entry_init( + self, event, entry_type=proc, + specific=proc.get_regex(os.path.basename(self.path))) + else: + if event.filename in self.entries: + logger.warn("Got duplicate add for %s" % event.filename) + else: + fpath = os.path.join(self.path, event.filename) + self.entries[event.filename] = proc(fpath) + self.entries[event.filename].handle_event(event) + + def bind_entry(self, entry, metadata): + info_handlers = [] + generators = [] + filters = [] + verifiers = [] + for ent in self.entries.values(): + if ent.__specific__ and not ent.specific.matches(metadata): + continue + if isinstance(ent, CfgInfo): + info_handlers.append(ent) + elif isinstance(ent, CfgGenerator): + generators.append(ent) + elif isinstance(ent, CfgFilter): + filters.append(ent) + elif isinstance(ent, CfgVerifier): + verifiers.append(ent) + + DEFAULT_INFO.bind_info_to_entry(entry, metadata) + if len(info_handlers) > 1: + logger.error("More than one info supplier found for %s: %s" % + (self.name, info_handlers)) + if len(info_handlers): + info_handlers[0].bind_info_to_entry(entry, metadata) + if entry.tag == 'Path': + entry.set('type', 'file') + + generator = self.best_matching(metadata, generators) + if entry.get('perms').lower() == 'inherit': + # use on-disk permissions + fname = os.path.join(self.path, generator.name) + entry.set('perms', + str(oct(stat.S_IMODE(os.stat(fname).st_mode)))) + try: + data = generator.get_data(entry, metadata) + except: + msg = "Cfg: exception rendering %s with %s: %s" % \ + (entry.get("name"), generator, sys.exc_info()[1]) + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + for fltr in filters: + data = fltr.modify_data(entry, metadata, data) + + if SETUP['validate']: + # we can have multiple verifiers, but we only want to use the + # best matching verifier of each class + verifiers_by_class = dict() + for verifier in verifiers: + cls = verifier.__class__.__name__ + if cls not in verifiers_by_class: + verifiers_by_class[cls] = [verifier] + else: + verifiers_by_class[cls].append(verifier) + for verifiers in verifiers_by_class.values(): + verifier = self.best_matching(metadata, verifiers) + try: + verifier.verify_entry(entry, metadata, data) + except CfgVerificationError: + msg = "Data for %s for %s failed to verify: %s" % \ + (entry.get('name'), metadata.hostname, + sys.exc_info()[1]) + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + if entry.get('encoding') == 'base64': + data = binascii.b2a_base64(data) + else: + try: + data = u_str(data, self.encoding) + except UnicodeDecodeError: + msg = "Failed to decode %s: %s" % (entry.get('name'), + sys.exc_info()[1]) + logger.error(msg) + logger.error("Please verify you are using the proper encoding.") + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + except ValueError: + msg = "Error in specification for %s: %s" % (entry.get('name'), + sys.exc_info()[1]) + logger.error(msg) + logger.error("You need to specify base64 encoding for %s." % + entry.get('name')) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + if data: + entry.text = data + else: + entry.set('empty', 'true') + + def list_accept_choices(self, entry, metadata): + '''return a list of candidate pull locations''' + generators = [ent for ent in list(self.entries.values()) + if (isinstance(ent, CfgGenerator) and + ent.specific.matches(metadata))] + if not generators: + msg = "No base file found for %s" % entry.get('name') + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + rv = [] + try: + best = self.best_matching(metadata, generators) + rv.append(best.specific) + except: + pass + + if not rv or not rv[0].hostname: + rv.append(Bcfg2.Server.Plugin.Specificity(hostname=metadata.hostname)) + return rv + + def build_filename(self, specific): + bfname = self.path + '/' + self.path.split('/')[-1] + if specific.all: + return bfname + elif specific.group: + return "%s.G%02d_%s" % (bfname, specific.prio, specific.group) + elif specific.hostname: + return "%s.H_%s" % (bfname, specific.hostname) + + def write_update(self, specific, new_entry, log): + if 'text' in new_entry: + name = self.build_filename(specific) + if os.path.exists("%s.genshi" % name): + msg = "Cfg: Unable to pull data for genshi types" + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + elif os.path.exists("%s.cheetah" % name): + msg = "Cfg: Unable to pull data for cheetah types" + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + try: + etext = new_entry['text'].encode(self.encoding) + except: + msg = "Cfg: Cannot encode content of %s as %s" % (name, + self.encoding) + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + open(name, 'w').write(etext) + self.debug_log("Wrote file %s" % name, flag=log) + badattr = [attr for attr in ['owner', 'group', 'perms'] + if attr in new_entry] + if badattr: + # check for info files and inform user of their removal + if os.path.exists(self.path + "/:info"): + logger.info("Removing :info file and replacing with " + "info.xml") + os.remove(self.path + "/:info") + if os.path.exists(self.path + "/info"): + logger.info("Removing info file and replacing with " + "info.xml") + os.remove(self.path + "/info") + metadata_updates = {} + metadata_updates.update(self.metadata) + for attr in badattr: + metadata_updates[attr] = new_entry.get(attr) + infoxml = lxml.etree.Element('FileInfo') + infotag = lxml.etree.SubElement(infoxml, 'Info') + [infotag.attrib.__setitem__(attr, metadata_updates[attr]) \ + for attr in metadata_updates] + ofile = open(self.path + "/info.xml", "w") + ofile.write(lxml.etree.tostring(infoxml, pretty_print=True)) + ofile.close() + self.debug_log("Wrote file %s" % (self.path + "/info.xml"), + flag=log) + + +class Cfg(Bcfg2.Server.Plugin.GroupSpool, + Bcfg2.Server.Plugin.PullTarget): + """This generator in the configuration file repository for Bcfg2.""" + name = 'Cfg' + __author__ = 'bcfg-dev@mcs.anl.gov' + es_cls = CfgEntrySet + es_child_cls = Bcfg2.Server.Plugin.SpecificData + + def __init__(self, core, datastore): + global SETUP + Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore) + Bcfg2.Server.Plugin.PullTarget.__init__(self) + + SETUP = core.setup + if 'validate' not in SETUP: + SETUP['validate'] = Bcfg2.Options.CFG_VALIDATION + SETUP.reparse() + + def AcceptChoices(self, entry, metadata): + return self.entries[entry.get('name')].list_accept_choices(entry, + metadata) + + def AcceptPullData(self, specific, new_entry, log): + return self.entries[new_entry.get('name')].write_update(specific, + new_entry, + log) diff --git a/src/lib/Server/Plugins/Cvs.py b/src/lib/Bcfg2/Server/Plugins/Cvs.py index ea898c023..6ce72acd2 100644 --- a/src/lib/Server/Plugins/Cvs.py +++ b/src/lib/Bcfg2/Server/Plugins/Cvs.py @@ -10,7 +10,6 @@ class Cvs(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """CVS is a version plugin for dealing with Bcfg2 repository.""" name = 'Cvs' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/DBStats.py b/src/lib/Bcfg2/Server/Plugins/DBStats.py index 8761d282d..999e078b9 100644 --- a/src/lib/Server/Plugins/DBStats.py +++ b/src/lib/Bcfg2/Server/Plugins/DBStats.py @@ -22,7 +22,6 @@ class DBStats(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.ThreadedStatistics, Bcfg2.Server.Plugin.PullSource): name = 'DBStats' - __version__ = '$Id$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) @@ -100,6 +99,8 @@ class DBStats(Bcfg2.Server.Plugin.Plugin, ret.append(getattr(entry.reason, "current_%s" % t)) if entry.reason.is_sensitive: raise Bcfg2.Server.Plugin.PluginExecutionError + elif len(entry.reason.unpruned) != 0: + ret.append('\n'.join(entry.reason.unpruned)) elif entry.reason.current_diff != '': if entry.reason.is_binary: ret.append(binascii.a2b_base64(entry.reason.current_diff)) diff --git a/src/lib/Server/Plugins/Darcs.py b/src/lib/Bcfg2/Server/Plugins/Darcs.py index eb34a52c4..9fb9ff4f1 100644 --- a/src/lib/Server/Plugins/Darcs.py +++ b/src/lib/Bcfg2/Server/Plugins/Darcs.py @@ -10,7 +10,6 @@ class Darcs(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Darcs is a version plugin for dealing with Bcfg2 repos.""" name = 'Darcs' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/Decisions.py b/src/lib/Bcfg2/Server/Plugins/Decisions.py index 556f75502..b432474f2 100644 --- a/src/lib/Server/Plugins/Decisions.py +++ b/src/lib/Bcfg2/Server/Plugins/Decisions.py @@ -50,7 +50,6 @@ class Decisions(DecisionSet, Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Decision): name = 'Decisions' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Defaults.py b/src/lib/Bcfg2/Server/Plugins/Defaults.py index b208c1126..718192e2a 100644 --- a/src/lib/Server/Plugins/Defaults.py +++ b/src/lib/Bcfg2/Server/Plugins/Defaults.py @@ -1,5 +1,4 @@ """This generator provides rule-based entry mappings.""" -__revision__ = '$Revision$' import re import Bcfg2.Server.Plugin @@ -9,7 +8,6 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules, Bcfg2.Server.Plugin.StructureValidator): """Set default attributes on bound entries""" name = 'Defaults' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' # Rules is a Generator that happens to implement all of the diff --git a/src/lib/Server/Plugins/Deps.py b/src/lib/Bcfg2/Server/Plugins/Deps.py index 482d457af..9b848baae 100644 --- a/src/lib/Server/Plugins/Deps.py +++ b/src/lib/Bcfg2/Server/Plugins/Deps.py @@ -1,5 +1,4 @@ """This plugin provides automatic dependency handling.""" -__revision__ = '$Revision$' import lxml.etree @@ -45,7 +44,6 @@ class DepXMLSrc(Bcfg2.Server.Plugin.XMLSrc): class Deps(Bcfg2.Server.Plugin.PrioDir, Bcfg2.Server.Plugin.StructureValidator): name = 'Deps' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = DepXMLSrc diff --git a/src/lib/Server/Plugins/Editor.py b/src/lib/Bcfg2/Server/Plugins/Editor.py index 76a03a325..c0d2cfbad 100644 --- a/src/lib/Server/Plugins/Editor.py +++ b/src/lib/Bcfg2/Server/Plugins/Editor.py @@ -59,7 +59,6 @@ class EditEntrySet(Bcfg2.Server.Plugin.EntrySet): class Editor(Bcfg2.Server.Plugin.GroupSpool, Bcfg2.Server.Plugin.Probing): name = 'Editor' - __version__ = '$Id$' __author__ = 'bcfg2-dev@mcs.anl.gov' filename_pattern = 'edits' es_child_cls = EditDirectives diff --git a/src/lib/Server/Plugins/FileProbes.py b/src/lib/Bcfg2/Server/Plugins/FileProbes.py index 2332a1d40..556965fca 100644 --- a/src/lib/Server/Plugins/FileProbes.py +++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py @@ -3,7 +3,6 @@ added to the specification. On subsequent runs, the file will be replaced on the client if it is missing; if it has changed on the client, it can either be updated in the specification or replaced on the client """ -__revision__ = '$Revision: 1465 $' import os import sys @@ -56,7 +55,6 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, name = 'FileProbes' experimental = True - __version__ = '$Id$' __author__ = 'chris.a.st.pierre@gmail.com' def __init__(self, core, datastore): @@ -136,7 +134,7 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, else: entrydata = entry.text - if create: + if create: self.logger.info("Writing new probed file %s" % fileloc) self.write_file(fileloc, contents) self.verify_file(filename, contents, metadata) @@ -196,7 +194,7 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, except Bcfg2.Server.Plugin.PluginExecutionError: tries += 1 continue - + # get current entry data if entry.get("encoding") == "base64": entrydata = binascii.a2b_base64(entry.text) @@ -205,12 +203,12 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, if entrydata == contents: updated = True tries += 1 - + def write_infoxml(self, infoxml, entry, data): """ write an info.xml for the file """ if os.path.exists(infoxml): return - + self.logger.info("Writing info.xml at %s for %s" % (infoxml, data.get("name"))) info = \ @@ -223,7 +221,7 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, Bcfg2.Options.MDATA_PERMS.value), encoding=entry.get("encoding", Bcfg2.Options.ENCODING.value)) - + root = lxml.etree.Element("FileInfo") root.append(info) try: diff --git a/src/lib/Server/Plugins/Fossil.py b/src/lib/Bcfg2/Server/Plugins/Fossil.py index 57d427673..1b1627688 100644 --- a/src/lib/Server/Plugins/Fossil.py +++ b/src/lib/Bcfg2/Server/Plugins/Fossil.py @@ -10,7 +10,6 @@ class Fossil(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Fossil is a version plugin for dealing with Bcfg2 repos.""" name = 'Fossil' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Git.py b/src/lib/Bcfg2/Server/Plugins/Git.py index aaeac12ae..8f8ea87f1 100644 --- a/src/lib/Server/Plugins/Git.py +++ b/src/lib/Bcfg2/Server/Plugins/Git.py @@ -13,7 +13,6 @@ class Git(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Git is a version plugin for dealing with Bcfg2 repos.""" name = 'Git' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/GroupPatterns.py b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py index 58b4d4afb..58b4d4afb 100644 --- a/src/lib/Server/Plugins/GroupPatterns.py +++ b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py diff --git a/src/lib/Server/Plugins/Guppy.py b/src/lib/Bcfg2/Server/Plugins/Guppy.py index b217378d6..eea92f30f 100644 --- a/src/lib/Server/Plugins/Guppy.py +++ b/src/lib/Bcfg2/Server/Plugins/Guppy.py @@ -12,7 +12,7 @@ python -c "from guppy import hpy;hpy().monitor()" For example: # python -c "from guppy import hpy;hpy().monitor()" -<Monitor> +<Monitor> *** Connection 1 opened *** <Monitor> lc CID PID ARGV @@ -21,7 +21,7 @@ CID PID ARGV Remote connection 1. To return to Monitor, type <Ctrl-C> or .<RETURN> <Annex> int Remote interactive console. To return to Annex, type '-'. ->>> hp.heap() +>>> hp.heap() ... @@ -32,7 +32,6 @@ 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 diff --git a/src/lib/Server/Plugins/Hg.py b/src/lib/Bcfg2/Server/Plugins/Hg.py index 70e33ef1f..0c3537613 100644 --- a/src/lib/Server/Plugins/Hg.py +++ b/src/lib/Bcfg2/Server/Plugins/Hg.py @@ -10,7 +10,6 @@ class Hg(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Mercurial is a version plugin for dealing with Bcfg2 repository.""" name = 'Mercurial' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/Hostbase.py b/src/lib/Bcfg2/Server/Plugins/Hostbase.py index 4180fd716..e9c1c1cff 100644 --- a/src/lib/Server/Plugins/Hostbase.py +++ b/src/lib/Bcfg2/Server/Plugins/Hostbase.py @@ -2,7 +2,6 @@ This file provides the Hostbase plugin. It manages dns/dhcp/nis host information """ -__revision__ = '$Revision$' import os os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Hostbase.settings' @@ -23,7 +22,6 @@ class Hostbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Generator): """The Hostbase plugin handles host/network info.""" name = 'Hostbase' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filepath = '/my/adm/hostbase/files/bind' diff --git a/src/lib/Server/Plugins/Ldap.py b/src/lib/Bcfg2/Server/Plugins/Ldap.py index f1e2198d2..29abf5b13 100644 --- a/src/lib/Server/Plugins/Ldap.py +++ b/src/lib/Bcfg2/Server/Plugins/Ldap.py @@ -50,7 +50,7 @@ class ConfigFile(Bcfg2.Server.Plugin.FileBacked): def Index(self): """ Reregisters the queries in the config file - + The config will take care of actually registering the queries, so we just load it once and don't keep it. """ @@ -63,24 +63,23 @@ class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): The Ldap plugin allows adding data from an LDAP server to your metadata. """ name = "Ldap" - version = "$Revision: $" experimental = True debug_flag = False - + def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) Bcfg2.Server.Plugin.Connector.__init__(self) self.config = ConfigFile(self.data + "/config.py", core.fam) - + def debug_log(self, message, flag = None): if (flag is None) and self.debug_flag or flag: self.logger.error(message) - + def get_additional_data(self, metadata): query = None try: data = {} - self.debug_log("LdapPlugin debug: found queries " + + self.debug_log("LdapPlugin debug: found queries " + str(LDAP_QUERIES)) for QueryClass in LDAP_QUERIES: query = QueryClass() @@ -95,14 +94,14 @@ class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): except Exception: if hasattr(query, "name"): Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + - "Exception during processing of query named '" + + "Exception during processing of query named '" + str(query.name) + - "', query results will be empty" + + "', query results will be empty" + " and may cause bind failures") for line in traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]): - Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + + Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + line.replace("\n", "")) return {} @@ -110,23 +109,23 @@ class LdapConnection(object): """ Connection to an LDAP server. """ - def __init__(self, host = "localhost", port = 389, + def __init__(self, host = "localhost", port = 389, binddn = None, bindpw = None): self.host = host self.port = port self.binddn = binddn self.bindpw = bindpw self.conn = None - + def __del__(self): if self.conn: self.conn.unbind() - + def init_conn(self): self.conn = ldap.initialize(self.url) if self.binddn is not None and self.bindpw is not None: self.conn.simple_bind_s(self.binddn, self.bindpw) - + def run_query(self, query): result = None for attempt in range(RETRY_COUNT + 1): @@ -148,17 +147,17 @@ class LdapConnection(object): self.conn = None time.sleep(RETRY_DELAY) return result - + @property def url(self): return "ldap://" + self.host + ":" + str(self.port) - + class LdapQuery(object): """ Query referencing an LdapConnection and providing several methods for query manipulation. """ - + name = "unknown" base = "" scope = "sub" @@ -166,10 +165,10 @@ class LdapQuery(object): attrs = None connection = None result = None - + def __unicode__(self): return "LdapQuery:" + self.name - + def is_applicable(self, metadata): """ Overrideable method to determine if the query is to be executed for @@ -177,27 +176,27 @@ class LdapQuery(object): Defaults to true. """ return True - + def prepare_query(self, metadata): """ Overrideable method to alter the query based on metadata. Defaults to doing nothing. In most cases, you will do something like - + self.filter = "(cn=" + metadata.hostname + ")" - + here. """ pass - + def process_result(self, metadata): """ Overrideable method to post-process the query result. Defaults to returning the unaltered result. """ return self.result - + def get_result(self, metadata): """ Method to handle preparing, executing and processing the query. @@ -208,7 +207,7 @@ class LdapQuery(object): self.result = self.process_result(metadata) return self.result else: - Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + + Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + "No valid connection defined for query " + str(self)) return None @@ -224,14 +223,14 @@ class LdapSubQuery(LdapQuery): Defaults to doing nothing. """ pass - + def process_result(self, metadata, **kwargs): """ Overrideable method to post-process the query result. Defaults to returning the unaltered result. """ return self.result - + def get_result(self, metadata, **kwargs): """ Method to handle preparing, executing and processing the query. @@ -241,6 +240,6 @@ class LdapSubQuery(LdapQuery): self.result = self.connection.run_query(self) return self.process_result(metadata, **kwargs) else: - Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + + Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + "No valid connection defined for query " + str(self)) return None diff --git a/src/lib/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py index 4f0ca9686..0cb4dc087 100644 --- a/src/lib/Server/Plugins/Metadata.py +++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py @@ -2,8 +2,6 @@ This file stores persistent metadata for the Bcfg2 Configuration Repository. """ -__revision__ = '$Revision$' - import copy import fcntl import lxml.etree @@ -37,13 +35,16 @@ class MetadataRuntimeError(Exception): pass -class XMLMetadataConfig(object): +class XMLMetadataConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked): """Handles xml config files and all XInclude statements""" def __init__(self, metadata, watch_clients, basefile): + Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, + os.path.join(metadata.data, + basefile), + metadata.core.fam) self.metadata = metadata self.basefile = basefile self.should_monitor = watch_clients - self.extras = [] self.data = None self.basedata = None self.basedir = metadata.data @@ -63,11 +64,10 @@ class XMLMetadataConfig(object): raise MetadataRuntimeError return self.basedata - def add_monitor(self, fname): + def add_monitor(self, fpath, fname): """Add a fam monitor for an included file""" if self.should_monitor: - self.metadata.core.fam.AddMonitor("%s/%s" % (self.basedir, fname), - self.metadata) + self.metadata.core.fam.AddMonitor(fpath, self.metadata) self.extras.append(fname) def load_xml(self): @@ -76,31 +76,29 @@ class XMLMetadataConfig(object): xdata = lxml.etree.parse(os.path.join(self.basedir, self.basefile), parser=Bcfg2.Server.XMLParser) except lxml.etree.XMLSyntaxError: - self.logger.error('Failed to parse %s' % (self.basefile)) + self.logger.error('Failed to parse %s' % self.basefile) return + self.extras = [] self.basedata = copy.copy(xdata) - included = [ent.get('href') for ent in \ - xdata.findall('./{http://www.w3.org/2001/XInclude}include')] - if included: - for name in included: - if name not in self.extras: - self.add_monitor(name) + self._follow_xincludes(xdata=xdata) + if self.extras: try: xdata.xinclude() except lxml.etree.XIncludeError: - self.logger.error("Failed to process XInclude for file %s" % self.basefile) + self.logger.error("Failed to process XInclude for file %s" % + self.basefile) self.data = xdata def write(self): """Write changes to xml back to disk.""" - self.write_xml("%s/%s" % (self.basedir, self.basefile), + self.write_xml(os.path.join(self.basedir, self.basefile), self.basedata) def write_xml(self, fname, xmltree): """Write changes to xml back to disk.""" tmpfile = "%s.new" % fname try: - datafile = open("%s" % tmpfile, 'w') + datafile = open(tmpfile, 'w') except IOError: e = sys.exc_info()[1] self.logger.error("Failed to write %s: %s" % (tmpfile, e)) @@ -116,30 +114,30 @@ class XMLMetadataConfig(object): datafile.write(newcontents) except: fcntl.lockf(fd, fcntl.LOCK_UN) - self.logger.error("Metadata: Failed to write new xml data to %s" % tmpfile, exc_info=1) - os.unlink("%s" % tmpfile) + self.logger.error("Metadata: Failed to write new xml data to %s" % + tmpfile, exc_info=1) + os.unlink(tmpfile) raise MetadataRuntimeError datafile.close() # check if clients.xml is a symlink - xmlfile = "%s" % fname - if os.path.islink(xmlfile): - xmlfile = os.readlink(xmlfile) + if os.path.islink(fname): + fname = os.readlink(fname) try: - os.rename("%s" % tmpfile, xmlfile) + os.rename(tmpfile, fname) except: self.logger.error("Metadata: Failed to rename %s" % tmpfile) raise MetadataRuntimeError def find_xml_for_xpath(self, xpath): - """Find and load xml data containing the xpath query""" + """Find and load xml file containing the xpath query""" if self.pseudo_monitor: # Reload xml if we don't have a real monitor self.load_xml() cli = self.basedata.xpath(xpath) if len(cli) > 0: - return {'filename': "%s/%s" % (self.basedir, self.basefile), + return {'filename': os.path.join(self.basedir, self.basefile), 'xmltree': self.basedata, 'xquery': cli} else: @@ -224,7 +222,6 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Metadata, Bcfg2.Server.Plugin.Statistics): """This class contains data for bcfg2 server metadata.""" - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' name = "Metadata" sort_order = 500 @@ -235,8 +232,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Statistics.__init__(self) if watch_clients: try: - core.fam.AddMonitor("%s/%s" % (self.data, "groups.xml"), self) - core.fam.AddMonitor("%s/%s" % (self.data, "clients.xml"), self) + core.fam.AddMonitor(os.path.join(self.data, "groups.xml"), self) + core.fam.AddMonitor(os.path.join(self.data, "clients.xml"), self) except: print("Unable to add file monitor for groups.xml or clients.xml") raise Bcfg2.Server.Plugin.PluginInitError @@ -277,272 +274,242 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, @classmethod def init_repo(cls, repo, groups, os_selection, clients): - path = '%s/%s' % (repo, cls.name) + path = os.path.join(repo, cls.name) os.makedirs(path) - open("%s/Metadata/groups.xml" % - repo, "w").write(groups % os_selection) - open("%s/Metadata/clients.xml" % - repo, "w").write(clients % socket.getfqdn()) + open(os.path.join(repo, "Metadata", "groups.xml"), + "w").write(groups % os_selection) + open(os.path.join(repo, "Metadata", "clients.xml"), + "w").write(clients % socket.getfqdn()) def get_groups(self): '''return groups xml tree''' - groups_tree = lxml.etree.parse(self.data + "/groups.xml", + groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"), parser=Bcfg2.Server.XMLParser) root = groups_tree.getroot() return root - def search_group(self, group_name, tree): - """Find a group.""" - for node in tree.findall("//Group"): - if node.get("name") == group_name: + def _search_xdata(self, tag, name, tree, alias=False): + for node in tree.findall("//%s" % tag): + if node.get("name") == name: return node - for child in node: - if child.tag == "Alias" and child.attrib["name"] == group_name: - return node + elif alias: + for child in node: + if (child.tag == "Alias" and + child.attrib["name"] == name): + return node return None - def add_group(self, group_name, attribs): - """Add group to groups.xml.""" + def search_group(self, group_name, tree): + """Find a group.""" + return self._search_xdata("Group", group_name, tree) + + def search_bundle(self, bundle_name, tree): + """Find a bundle.""" + return self._search_xdata("Bundle", bundle_name, tree) + + def search_client(self, client_name, tree): + return self._search_xdata("Client", client_name, tree, alias=True) - node = self.search_group(group_name, self.groups_xml.xdata) + def _add_xdata(self, config, tag, name, attribs=None, alias=False): + node = self._search_xdata(tag, name, config.xdata, alias=alias) if node != None: - self.logger.error("Group \"%s\" already exists" % (group_name)) + self.logger.error("%s \"%s\" already exists" % (tag, name)) raise MetadataConsistencyError + element = lxml.etree.SubElement(config.base_xdata.getroot(), + tag, name=name) + if attribs: + for key, val in list(attribs.items()): + element.set(key, val) + config.write() - element = lxml.etree.SubElement(self.groups_xml.base_xdata.getroot(), - "Group", name=group_name) - for key, val in list(attribs.items()): - element.set(key, val) - self.groups_xml.write() + def add_group(self, group_name, attribs): + """Add group to groups.xml.""" + return self._add_xdata(self.groups_xml, "Group", group_name, + attribs=attribs) - def update_group(self, group_name, attribs): - """Update a groups attributes.""" - node = self.search_group(group_name, self.groups_xml.xdata) + def add_bundle(self, bundle_name): + """Add bundle to groups.xml.""" + return self._add_xdata(self.groups_xml, "Bundle", bundle_name) + + def add_client(self, client_name, attribs): + """Add client to clients.xml.""" + return self._add_xdata(self.clients_xml, "Client", client_name, + attribs=attribs, alias=True) + + def _update_xdata(self, config, tag, name, attribs, alias=False): + node = self._search_xdata(tag, name, config.xdata, alias=alias) if node == None: - self.logger.error("Group \"%s\" does not exist" % (group_name)) + self.logger.error("%s \"%s\" does not exist" % (tag, name)) raise MetadataConsistencyError - xdict = self.groups_xml.find_xml_for_xpath('.//Group[@name="%s"]' % (node.get('name'))) + xdict = config.find_xml_for_xpath('.//%s[@name="%s"]' % + (tag, node.get('name'))) if not xdict: - self.logger.error("Unexpected error finding group") + self.logger.error("Unexpected error finding %s \"%s\"" % + (tag, name)) raise MetadataConsistencyError - for key, val in list(attribs.items()): xdict['xquery'][0].set(key, val) - self.groups_xml.write_xml(xdict['filename'], xdict['xmltree']) + config.write_xml(xdict['filename'], xdict['xmltree']) - def remove_group(self, group_name): - """Remove a group.""" - node = self.search_group(group_name, self.groups_xml.xdata) + def update_group(self, group_name, attribs): + """Update a groups attributes.""" + return self._update_xdata(self.groups_xml, "Group", group_name, attribs) + + def update_client(self, client_name, attribs): + """Update a clients attributes.""" + return self._update_xdata(self.clients_xml, "Client", client_name, + attribs, alias=True) + + def _remove_xdata(self, config, tag, name, alias=False): + node = self._search_xdata(tag, name, config.xdata) if node == None: - self.logger.error("Group \"%s\" does not exist" % (group_name)) + self.logger.error("%s \"%s\" does not exist" % (tag, name)) raise MetadataConsistencyError - xdict = self.groups_xml.find_xml_for_xpath('.//Group[@name="%s"]' % (node.get('name'))) + xdict = config.find_xml_for_xpath('.//%s[@name="%s"]' % + (tag, node.get('name'))) if not xdict: - self.logger.error("Unexpected error finding group") + self.logger.error("Unexpected error finding %s \"%s\"" % + (tag, name)) raise MetadataConsistencyError xdict['xquery'][0].getparent().remove(xdict['xquery'][0]) self.groups_xml.write_xml(xdict['filename'], xdict['xmltree']) - def add_bundle(self, bundle_name): - """Add bundle to groups.xml.""" - tree = lxml.etree.parse(self.data + "/groups.xml", - parser=Bcfg2.Server.XMLParser) - root = tree.getroot() - element = lxml.etree.Element("Bundle", name=bundle_name) - node = self.search_group(bundle_name, tree) - if node != None: - self.logger.error("Bundle \"%s\" already exists" % (bundle_name)) - raise MetadataConsistencyError - root.append(element) - group_tree = open(self.data + "/groups.xml", "w") - fd = group_tree.fileno() - while True: - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - continue - else: - break - tree.write(group_tree) - fcntl.lockf(fd, fcntl.LOCK_UN) - group_tree.close() + def remove_group(self, group_name): + """Remove a group.""" + return self._remove_xdata(self.groups_xml, "Group", group_name) def remove_bundle(self, bundle_name): """Remove a bundle.""" - tree = lxml.etree.parse(self.data + "/groups.xml", - parser=Bcfg2.Server.XMLParser) - root = tree.getroot() - node = self.search_group(bundle_name, tree) - if node == None: - self.logger.error("Bundle \"%s\" not found" % (bundle_name)) - raise MetadataConsistencyError - root.remove(node) - group_tree = open(self.data + "/groups.xml", "w") - fd = group_tree.fileno() - while True: - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - continue - else: - break - tree.write(group_tree) - fcntl.lockf(fd, fcntl.LOCK_UN) - group_tree.close() - - def search_client(self, client_name, tree): - """Find a client.""" - for node in tree.findall("//Client"): - if node.get("name") == client_name: - return node - for child in node: - if child.tag == "Alias" and child.attrib["name"] == client_name: - return node - return None - - def add_client(self, client_name, attribs): - """Add client to clients.xml.""" - node = self.search_client(client_name, self.clients_xml.xdata) - if node != None: - self.logger.error("Client \"%s\" already exists" % (client_name)) - raise MetadataConsistencyError - - element = lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - "Client", name=client_name) - for key, val in list(attribs.items()): - element.set(key, val) - self.clients_xml.write() - - def update_client(self, client_name, attribs): - """Update a clients attributes.""" - node = self.search_client(client_name, self.clients_xml.xdata) - if node == None: - self.logger.error("Client \"%s\" does not exist" % (client_name)) - raise MetadataConsistencyError - - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (node.get('name'))) - if not xdict: - self.logger.error("Unexpected error finding client") - raise MetadataConsistencyError + return self._remove_xdata(self.groups_xml, "Bundle", bundle_name) - node = xdict['xquery'][0] - [node.set(key, value) for key, value in list(attribs.items())] - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + def _handle_clients_xml_event(self, event): + xdata = self.clients_xml.xdata + self.clients = {} + self.aliases = {} + self.raliases = {} + self.bad_clients = {} + self.secure = [] + self.floating = [] + self.addresses = {} + self.raddresses = {} + for client in xdata.findall('.//Client'): + clname = client.get('name').lower() + if 'address' in client.attrib: + caddr = client.get('address') + if caddr in self.addresses: + self.addresses[caddr].append(clname) + else: + self.addresses[caddr] = [clname] + if clname not in self.raddresses: + self.raddresses[clname] = set() + self.raddresses[clname].add(caddr) + if 'auth' in client.attrib: + self.auth[client.get('name')] = client.get('auth', + 'cert+password') + if 'uuid' in client.attrib: + self.uuid[client.get('uuid')] = clname + if client.get('secure', 'false') == 'true': + self.secure.append(clname) + if client.get('location', 'fixed') == 'floating': + self.floating.append(clname) + if 'password' in client.attrib: + self.passwords[clname] = client.get('password') + + self.raliases[clname] = set() + for alias in client.findall('Alias'): + self.aliases.update({alias.get('name'): clname}) + self.raliases[clname].add(alias.get('name')) + if 'address' not in alias.attrib: + continue + if alias.get('address') in self.addresses: + self.addresses[alias.get('address')].append(clname) + else: + self.addresses[alias.get('address')] = [clname] + if clname not in self.raddresses: + self.raddresses[clname] = set() + self.raddresses[clname].add(alias.get('address')) + self.clients.update({clname: client.get('profile')}) + self.states['clients.xml'] = True + + def _handle_groups_xml_event(self, event): + xdata = self.groups_xml.xdata + self.public = [] + self.private = [] + self.profiles = [] + self.groups = {} + grouptmp = {} + self.categories = {} + groupseen = list() + for group in xdata.xpath('//Groups/Group'): + if group.get('name') not in groupseen: + groupseen.append(group.get('name')) + else: + self.logger.error("Metadata: Group %s defined multiply" % + group.get('name')) + grouptmp[group.get('name')] = \ + ([item.get('name') for item in group.findall('./Bundle')], + [item.get('name') for item in group.findall('./Group')]) + grouptmp[group.get('name')][1].append(group.get('name')) + if group.get('default', 'false') == 'true': + self.default = group.get('name') + if group.get('profile', 'false') == 'true': + self.profiles.append(group.get('name')) + if group.get('public', 'false') == 'true': + self.public.append(group.get('name')) + elif group.get('public', 'true') == 'false': + self.private.append(group.get('name')) + if 'category' in group.attrib: + self.categories[group.get('name')] = group.get('category') + + for group in grouptmp: + # self.groups[group] => (bundles, groups, categories) + self.groups[group] = (set(), set(), {}) + tocheck = [group] + group_cat = self.groups[group][2] + while tocheck: + now = tocheck.pop() + self.groups[group][1].add(now) + if now in grouptmp: + (bundles, groups) = grouptmp[now] + for ggg in groups: + if ggg in self.groups[group][1]: + continue + if (ggg not in self.categories or \ + self.categories[ggg] not in self.groups[group][2]): + self.groups[group][1].add(ggg) + tocheck.append(ggg) + if ggg in self.categories: + group_cat[self.categories[ggg]] = ggg + elif ggg in self.categories: + self.logger.info("Group %s: %s cat-suppressed %s" % \ + (group, + group_cat[self.categories[ggg]], + ggg)) + [self.groups[group][0].add(bund) for bund in bundles] + self.states['groups.xml'] = True def HandleEvent(self, event): """Handle update events for data files.""" if self.clients_xml.HandleEvent(event): - xdata = self.clients_xml.xdata - self.clients = {} - self.aliases = {} - self.raliases = {} - self.bad_clients = {} - self.secure = [] - self.floating = [] - self.addresses = {} - self.raddresses = {} - for client in xdata.findall('.//Client'): - clname = client.get('name').lower() - if 'address' in client.attrib: - caddr = client.get('address') - if caddr in self.addresses: - self.addresses[caddr].append(clname) - else: - self.addresses[caddr] = [clname] - if clname not in self.raddresses: - self.raddresses[clname] = set() - self.raddresses[clname].add(caddr) - if 'auth' in client.attrib: - self.auth[client.get('name')] = client.get('auth', - 'cert+password') - if 'uuid' in client.attrib: - self.uuid[client.get('uuid')] = clname - if client.get('secure', 'false') == 'true': - self.secure.append(clname) - if client.get('location', 'fixed') == 'floating': - self.floating.append(clname) - if 'password' in client.attrib: - self.passwords[clname] = client.get('password') - for alias in [alias for alias in client.findall('Alias')\ - if 'address' in alias.attrib]: - if alias.get('address') in self.addresses: - self.addresses[alias.get('address')].append(clname) - else: - self.addresses[alias.get('address')] = [clname] - if clname not in self.raddresses: - self.raddresses[clname] = set() - self.raddresses[clname].add(alias.get('address')) - self.clients.update({clname: client.get('profile')}) - [self.aliases.update({alias.get('name'): clname}) \ - for alias in client.findall('Alias')] - self.raliases[clname] = set() - [self.raliases[clname].add(alias.get('name')) for alias \ - in client.findall('Alias')] - self.states['clients.xml'] = True + self._handle_clients_xml_event(event) elif self.groups_xml.HandleEvent(event): - xdata = self.groups_xml.xdata - self.public = [] - self.private = [] - self.profiles = [] - self.groups = {} - grouptmp = {} - self.categories = {} - groupseen = list() - for group in xdata.xpath('//Groups/Group'): - if group.get('name') not in groupseen: - groupseen.append(group.get('name')) - else: - self.logger.error("Metadata: Group %s defined multiply" % (group.get('name'))) - grouptmp[group.get('name')] = tuple([[item.get('name') for item in group.findall(spec)] - for spec in ['./Bundle', './Group']]) - grouptmp[group.get('name')][1].append(group.get('name')) - if group.get('default', 'false') == 'true': - self.default = group.get('name') - if group.get('profile', 'false') == 'true': - self.profiles.append(group.get('name')) - if group.get('public', 'false') == 'true': - self.public.append(group.get('name')) - elif group.get('public', 'true') == 'false': - self.private.append(group.get('name')) - if 'category' in group.attrib: - self.categories[group.get('name')] = group.get('category') - for group in grouptmp: - # self.groups[group] => (bundles, groups, categories) - self.groups[group] = (set(), set(), {}) - tocheck = [group] - group_cat = self.groups[group][2] - while tocheck: - now = tocheck.pop() - self.groups[group][1].add(now) - if now in grouptmp: - (bundles, groups) = grouptmp[now] - for ggg in [ggg for ggg in groups if ggg not in self.groups[group][1]]: - if ggg not in self.categories or \ - self.categories[ggg] not in self.groups[group][2]: - self.groups[group][1].add(ggg) - tocheck.append(ggg) - if ggg in self.categories: - group_cat[self.categories[ggg]] = ggg - elif ggg in self.categories: - self.logger.info("Group %s: %s cat-suppressed %s" % \ - (group, - group_cat[self.categories[ggg]], - ggg)) - [self.groups[group][0].add(bund) for bund in bundles] - self.states['groups.xml'] = True + self._handle_groups_xml_event(event) + if False not in list(self.states.values()): # check that all client groups are real and complete real = list(self.groups.keys()) for client in list(self.clients.keys()): if self.clients[client] not in self.profiles: - self.logger.error("Client %s set as nonexistent or incomplete group %s" \ - % (client, self.clients[client])) - self.logger.error("Removing client mapping for %s" % (client)) + self.logger.error("Client %s set as nonexistent or " + "incomplete group %s" % + (client, self.clients[client])) + self.logger.error("Removing client mapping for %s" % client) self.bad_clients[client] = self.clients[client] del self.clients[client] for bclient in list(self.bad_clients.keys()): if self.bad_clients[bclient] in self.profiles: - self.logger.info("Restored profile mapping for client %s" % bclient) + self.logger.info("Restored profile mapping for client %s" % + bclient) self.clients[bclient] = self.bad_clients[bclient] del self.bad_clients[bclient] @@ -552,42 +519,37 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if False in list(self.states.values()): raise MetadataRuntimeError if profile not in self.public: - self.logger.error("Failed to set client %s to private group %s" % (client, profile)) + self.logger.error("Failed to set client %s to private group %s" % + (client, profile)) raise MetadataConsistencyError if client in self.clients: - self.logger.info("Changing %s group from %s to %s" % (client, self.clients[client], profile)) - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (client)) - if not xdict: - self.logger.error("Metadata: Unable to update profile for client %s. Use of Xinclude?" % client) - raise MetadataConsistencyError - xdict['xquery'][0].set('profile', profile) - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + self.logger.info("Changing %s group from %s to %s" % + (client, self.clients[client], profile)) + self.update_client(client, dict(profile=profile)) else: - self.logger.info("Creating new client: %s, profile %s" % \ + self.logger.info("Creating new client: %s, profile %s" % (client, profile)) if addresspair in self.session_cache: # we are working with a uuid'd client - lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - 'Client', - name=self.session_cache[addresspair][1], - uuid=client, profile=profile, - address=addresspair[0]) + self.add_client(self.session_cache[addresspair][1], + dict(uuid=client, profile=profile, + address=addresspair[0])) else: - lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - 'Client', name=client, - profile=profile) + self.add_client(client, dict(profile=profile)) self.clients[client] = profile self.clients_xml.write() def resolve_client(self, addresspair, cleanup_cache=False): """Lookup address locally or in DNS to get a hostname.""" if addresspair in self.session_cache: - # client _was_ cached, so there can be some expired entries - # we need to clean them up to avoid potentially infinite memory swell + # client _was_ cached, so there can be some expired + # entries. we need to clean them up to avoid potentially + # infinite memory swell cache_ttl = 90 if cleanup_cache: - # remove entries for this client's IP address with _any_ port numbers - # - perhaps a priority queue could be faster? + # remove entries for this client's IP address with + # _any_ port numbers - perhaps a priority queue could + # be faster? curtime = time.time() for addrpair in self.session_cache.keys(): if addresspair[0] == addrpair[0]: @@ -595,13 +557,18 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if curtime - stamp > cache_ttl: del self.session_cache[addrpair] # return the cached data - (stamp, uuid) = self.session_cache[addresspair] - if time.time() - stamp < cache_ttl: - return self.session_cache[addresspair][1] + try: + (stamp, uuid) = self.session_cache[addresspair] + if time.time() - stamp < cache_ttl: + return self.session_cache[addresspair][1] + except KeyError: + # we cleaned all cached data for this client in cleanup_cache + pass address = addresspair[0] if address in self.addresses: if len(self.addresses[address]) != 1: - self.logger.error("Address %s has multiple reverse assignments; a uuid must be used" % (address)) + self.logger.error("Address %s has multiple reverse assignments; " + "a uuid must be used" % (address)) raise MetadataConsistencyError return self.addresses[address][0] try: @@ -626,7 +593,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, (bundles, groups, categories) = self.groups[profile] else: if self.default == None: - self.logger.error("Cannot set group for client %s; no default group set" % (client)) + self.logger.error("Cannot set group for client %s; " + "no default group set" % client) raise MetadataConsistencyError self.set_profile(client, self.default, (None, None)) profile = self.default @@ -641,7 +609,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, password = self.passwords[client] else: password = None - uuids = [item for item, value in list(self.uuid.items()) if value == client] + uuids = [item for item, value in list(self.uuid.items()) + if value == client] if uuids: uuid = uuids[0] else: @@ -655,7 +624,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, [newgroups.add(g) for g in ngroups if g not in newgroups] newcategories.update(ncategories) return ClientMetadata(client, profile, newgroups, newbundles, aliases, - addresses, newcategories, uuid, password, self.query) + addresses, newcategories, uuid, password, + self.query) def get_all_group_names(self): all_groups = set() @@ -679,22 +649,27 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, def merge_additional_groups(self, imd, groups): for group in groups: - if group in self.categories and \ - self.categories[group] in imd.categories: + if (group in self.categories and + self.categories[group] in imd.categories): continue - nb, ng, _ = self.groups.get(group, (list(), [group], dict())) - for b in nb: - if b not in imd.bundles: - imd.bundles.add(b) - for g in ng: - if g not in imd.groups: - if g in self.categories and \ - self.categories[g] in imd.categories: + newbundles, newgroups, _ = self.groups.get(group, + (list(), + [group], + dict())) + for newbundle in newbundles: + if newbundle not in imd.bundles: + imd.bundles.add(newbundle) + for newgroup in newgroups: + if newgroup not in imd.groups: + if (newgroup in self.categories and + self.categories[newgroup] in imd.categories): continue - if g in self.private: - self.logger.error("Refusing to add dynamic membership in private group %s for client %s" % (g, imd.hostname)) + if newgroup in self.private: + self.logger.error("Refusing to add dynamic membership " + "in private group %s for client %s" % + (newgroup, imd.hostname)) continue - imd.groups.add(g) + imd.groups.add(newgroup) def merge_additional_data(self, imd, source, data): if not hasattr(imd, source): @@ -709,18 +684,19 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, return True if address in self.addresses: if client in self.addresses[address]: - self.debug_log("Client %s matches address %s" % (client, address)) + self.debug_log("Client %s matches address %s" % + (client, address)) return True else: - self.logger.error("Got request for non-float client %s from %s" \ - % (client, address)) + self.logger.error("Got request for non-float client %s from %s" % + (client, address)) return False resolved = self.resolve_client(addresspair) if resolved.lower() == client.lower(): return True else: - self.logger.error("Got request for %s from incorrect address %s" \ - % (client, address)) + self.logger.error("Got request for %s from incorrect address %s" % + (client, address)) self.logger.error("Resolved to %s" % resolved) return False @@ -738,7 +714,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, try: client = self.resolve_client(address) except MetadataConsistencyError: - self.logger.error("Client %s failed to resolve; metadata problem" % (address[0])) + self.logger.error("Client %s failed to resolve; metadata problem" + % address[0]) return False else: id_method = 'uuid' @@ -770,10 +747,12 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if client not in self.passwords: if client in self.secure: - self.logger.error("Client %s in secure mode but has no password" % (address[0])) + self.logger.error("Client %s in secure mode but has no password" + % address[0]) return False if password != self.password: - self.logger.error("Client %s used incorrect global password" % (address[0])) + self.logger.error("Client %s used incorrect global password" % + address[0]) return False if client not in self.secure: if client in self.passwords: @@ -781,14 +760,14 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, else: plist = [self.password] if password not in plist: - self.logger.error("Client %s failed to use either allowed password" % \ - (address[0])) + self.logger.error("Client %s failed to use either allowed " + "password" % address[0]) return False else: # client in secure mode and has a client password if password != self.passwords[client]: - self.logger.error("Client %s failed to use client password in secure mode" % \ - (address[0])) + self.logger.error("Client %s failed to use client password in " + "secure mode" % address[0]) return False # populate the session cache if user.decode('utf-8') != 'root': @@ -799,13 +778,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, """Hook into statistics interface to toggle clients in bootstrap mode.""" client = meta.hostname if client in self.auth and self.auth[client] == 'bootstrap': - self.logger.info("Asserting client %s auth mode to cert" % client) - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (client)) - if not xdict: - self.logger.error("Metadata: Unable to update profile for client %s. Use of Xinclude?" % client) - raise MetadataConsistencyError - xdict['xquery'][0].set('auth', 'cert') - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + self.update_client(client, dict(auth='cert')) def viz(self, hosts, bundles, key, only_client, colors): """Admin mode viz support.""" @@ -820,8 +793,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, def include_group(group): return not only_client or group in clientmeta.groups - - groups_tree = lxml.etree.parse(self.data + "/groups.xml", + + groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml"), parser=Bcfg2.Server.XMLParser) try: groups_tree.xinclude() @@ -830,7 +803,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, (dest, sys.exc_info()[1])) groups = groups_tree.getroot() categories = {'default': 'grey83'} - viz_str = "" + viz_str = [] egroups = groups.findall("Group") + groups.findall('.//Groups/Group') for group in egroups: if not group.get('category') in categories: @@ -850,10 +823,10 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, instances[profile] = [client] for profile, clist in list(instances.items()): clist.sort() - viz_str += '''\t"%s-instances" [ label="%s", shape="record" ];\n''' \ - % (profile, '|'.join(clist)) - viz_str += '''\t"%s-instances" -> "group-%s";\n''' \ - % (profile, profile) + viz_str.append('"%s-instances" [ label="%s", shape="record" ];' % + (profile, '|'.join(clist))) + viz_str.append('"%s-instances" -> "group-%s";' % + (profile, profile)) if bundles: bundles = [] [bundles.append(bund.get('name')) \ @@ -862,8 +835,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, and include_bundle(bund.get('name'))] bundles.sort() for bundle in bundles: - viz_str += '''\t"bundle-%s" [ label="%s", shape="septagon"];\n''' \ - % (bundle, bundle) + viz_str.append('"bundle-%s" [ label="%s", shape="septagon"];' % + (bundle, bundle)) gseen = [] for group in egroups: if group.get('profile', 'false') == 'true': @@ -872,24 +845,25 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, style = "filled" gseen.append(group.get('name')) if include_group(group.get('name')): - viz_str += '\t"group-%s" [label="%s", style="%s", fillcolor=%s];\n' % \ - (group.get('name'), group.get('name'), style, group.get('color')) + viz_str.append('"group-%s" [label="%s", style="%s", fillcolor=%s];' % + (group.get('name'), group.get('name'), style, + group.get('color'))) if bundles: for bundle in group.findall('Bundle'): - viz_str += '\t"group-%s" -> "bundle-%s";\n' % \ - (group.get('name'), bundle.get('name')) - gfmt = '\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n' + viz_str.append('"group-%s" -> "bundle-%s";' % + (group.get('name'), bundle.get('name'))) + gfmt = '"group-%s" [label="%s", style="filled", fillcolor="grey83"];' for group in egroups: for parent in group.findall('Group'): if parent.get('name') not in gseen and include_group(parent.get('name')): - viz_str += gfmt % (parent.get('name'), parent.get('name')) + viz_str.append(gfmt % (parent.get('name'), + parent.get('name'))) gseen.append(parent.get("name")) if include_group(group.get('name')): - viz_str += '\t"group-%s" -> "group-%s" ;\n' % \ - (group.get('name'), parent.get('name')) + viz_str.append('"group-%s" -> "group-%s";' % + (group.get('name'), parent.get('name'))) if key: for category in categories: - viz_str += '''\t"''' + category + '''" [label="''' + category + \ - '''", shape="record", style="filled", fillcolor=''' + \ - categories[category] + '''];\n''' - return viz_str + viz_str.append('"%s" [label="%s", shape="record", style="filled", fillcolor="%s"];' % + (category, category, categories[category])) + return "\n".join("\t" + s for s in viz_str) diff --git a/src/lib/Server/Plugins/NagiosGen.py b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py index 287e1b0d3..d132b0ff4 100644 --- a/src/lib/Server/Plugins/NagiosGen.py +++ b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py @@ -20,14 +20,13 @@ class NagiosGenConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked, Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, filename, fam) Bcfg2.Server.Plugin.StructFile.__init__(self, filename) - + class NagiosGen(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Generator): """NagiosGen is a Bcfg2 plugin that dynamically generates Nagios configuration file based on Bcfg2 data. """ name = 'NagiosGen' - __version__ = '0.7' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): @@ -132,14 +131,14 @@ class NagiosGen(Bcfg2.Server.Plugin.Plugin, group_data = [] for host in host_configs: host_data.append(open(host, 'r').read()) - + for group in group_configs: group_name = re.sub("(-group.cfg|.*/(?=[^/]+))", "", group) if "\n".join(host_data).find(group_name) != -1: groupfile = open(group, 'r') group_data.append(groupfile.read()) groupfile.close() - + entry.text = "%s\n\n%s" % ("\n".join(group_data), "\n".join(host_data)) [entry.attrib.__setitem__(key, value) for (key, value) in list(self.server_attrib.items())] diff --git a/src/lib/Server/Plugins/Ohai.py b/src/lib/Bcfg2/Server/Plugins/Ohai.py index 5fff20d98..5fff20d98 100644 --- a/src/lib/Server/Plugins/Ohai.py +++ b/src/lib/Bcfg2/Server/Plugins/Ohai.py diff --git a/src/lib/Server/Plugins/Packages/Apt.py b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py index f76bf7fa1..49e9d417b 100644 --- a/src/lib/Server/Plugins/Packages/Apt.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py @@ -50,10 +50,9 @@ class AptSource(Source): def read_files(self): bdeps = dict() bprov = dict() + depfnames = ['Depends', 'Pre-Depends'] if self.recommended: - depfnames = ['Depends', 'Pre-Depends', 'Recommends'] - else: - depfnames = ['Depends', 'Pre-Depends'] + depfnames.append('Recommends') for fname in self.files: if not self.rawurl: barch = [x @@ -77,6 +76,8 @@ class AptSource(Source): pkgname = words[1].strip().rstrip() self.pkgnames.add(pkgname) bdeps[barch][pkgname] = [] + elif words[0] == 'Essential' and self.essential: + self.essentialpkgs.add(pkgname) elif words[0] in depfnames: vindex = 0 for dep in words[1].split(','): diff --git a/src/lib/Server/Plugins/Packages/Collection.py b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py index 32eeda1ec..3ea14ce75 100644 --- a/src/lib/Server/Plugins/Packages/Collection.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py @@ -1,3 +1,4 @@ +import sys import copy import logging import Bcfg2.Server.Plugin @@ -18,9 +19,16 @@ except ImportError: # startup. (It would also prevent machines that change groups from # working properly; e.g., if you reinstall a machine with a new OS, # then returning a cached Collection object would give the wrong -# sources to that client.) +# sources to that client.) These are keyed by the collection +# cachekey, a unique key identifying the collection by its _config_, +# which could be shared among multiple clients. collections = dict() +# cache mapping of hostname -> collection cachekey. this _is_ used to +# return a Collection object when one is requested, so each entry is +# very short-lived -- it's purged at the end of each client run. +clients = dict() + class Collection(Bcfg2.Server.Plugin.Debuggable): def __init__(self, metadata, sources, basepath, debug=False): """ don't call this directly; use the factory function """ @@ -32,11 +40,11 @@ class Collection(Bcfg2.Server.Plugin.Debuggable): self.virt_pkgs = dict() try: - self.config = sources[0].config + self.setup = sources[0].setup self.cachepath = sources[0].basepath self.ptype = sources[0].ptype except IndexError: - self.config = None + self.setup = None self.cachepath = None self.ptype = "unknown" @@ -97,6 +105,12 @@ class Collection(Bcfg2.Server.Plugin.Debuggable): return source.get_deps(self.metadata, package) return [] + def get_essential(self): + essential = set() + for source in self.sources: + essential |= source.essentialpkgs + return essential + def get_provides(self, package): for source in self.sources: providers = source.get_provides(self.metadata, package) @@ -284,9 +298,33 @@ class Collection(Bcfg2.Server.Plugin.Debuggable): def sort(self, cmp=None, key=None, reverse=False): self.sources.sort(cmp, key, reverse) +def get_collection_class(source_type): + modname = "Bcfg2.Server.Plugins.Packages.%s" % source_type.title() + + try: + module = sys.modules[modname] + except KeyError: + try: + module = __import__(modname).Server.Plugins.Packages + except ImportError: + msg = "Packages: Unknown source type %s" % source_type + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + try: + cclass = getattr(module, source_type.title() + "Collection") + except AttributeError: + msg = "Packages: No collection class found for %s sources" % source_type + logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + return cclass + def clear_cache(): global collections + global clients collections = dict() + clients = dict() def factory(metadata, sources, basepath, debug=False): global collections @@ -295,7 +333,10 @@ def factory(metadata, sources, basepath, debug=False): # if sources.xml has not received a FAM event yet, defer; # instantiate a dummy Collection object return Collection(metadata, [], basepath) - + + if metadata.hostname in clients: + return collections[clients[metadata.hostname]] + sclasses = set() relevant = list() @@ -320,24 +361,16 @@ def factory(metadata, sources, basepath, debug=False): metadata.hostname) cclass = Collection else: - stype = sclasses.pop().__name__.replace("Source", "") - try: - module = \ - getattr(__import__("Bcfg2.Server.Plugins.Packages.%s" % - stype.title()).Server.Plugins.Packages, - stype.title()) - cclass = getattr(module, "%sCollection" % stype.title()) - except ImportError: - logger.error("Packages: Unknown source type %s" % stype) - except AttributeError: - logger.warning("Packages: No collection class found for %s sources" - % stype) + cclass = get_collection_class(sclasses.pop().__name__.replace("Source", + "")) if debug: logger.error("Packages: Using %s for Collection of sources for %s" % (cclass.__name__, metadata.hostname)) collection = cclass(metadata, relevant, basepath, debug=debug) - collections[metadata.hostname] = collection + ckey = collection.cachekey + clients[metadata.hostname] = ckey + collections[ckey] = collection return collection diff --git a/src/lib/Server/Plugins/Packages/Pac.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py index 9db6b0535..99a090739 100644 --- a/src/lib/Server/Plugins/Packages/Pac.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py @@ -6,7 +6,7 @@ from Bcfg2.Server.Plugins.Packages.Source import Source class PacCollection(Collection): def get_group(self, group): - self.logger.warning("Packages: Package groups are not supported by APT") + self.logger.warning("Packages: Package groups are not supported by Pacman") return [] class PacSource(Source): @@ -51,10 +51,9 @@ class PacSource(Source): bdeps = dict() bprov = dict() + depfnames = ['Depends', 'Pre-Depends'] if self.recommended: - depfnames = ['Depends', 'Pre-Depends', 'Recommends'] - else: - depfnames = ['Depends', 'Pre-Depends'] + depfnames.append('Recommends') for fname in self.files: if not self.rawurl: @@ -63,7 +62,7 @@ class PacSource(Source): # RawURL entries assume that they only have one <Arch></Arch> # element and that it is the architecture of the source. barch = self.arches[0] - + if barch not in bdeps: bdeps[barch] = dict() bprov[barch] = dict() diff --git a/src/lib/Server/Plugins/Packages/PackagesSources.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py index d399838ae..a6924b5d0 100644 --- a/src/lib/Server/Plugins/Packages/PackagesSources.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py @@ -8,8 +8,8 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked, Bcfg2.Server.Plugin.StructFile, Bcfg2.Server.Plugin.Debuggable): __identifier__ = None - - def __init__(self, filename, cachepath, fam, packages, config): + + def __init__(self, filename, cachepath, fam, packages, setup): Bcfg2.Server.Plugin.Debuggable.__init__(self) try: Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, @@ -24,7 +24,7 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked, raise Bcfg2.Server.Plugin.PluginInitError(msg) Bcfg2.Server.Plugin.StructFile.__init__(self, filename) self.cachepath = cachepath - self.config = config + self.setup = setup if not os.path.exists(self.cachepath): # create cache directory if needed try: @@ -43,10 +43,20 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked, def HandleEvent(self, event=None): Bcfg2.Server.Plugin.SingleXMLFileBacked.HandleEvent(self, event=event) - if event.filename != self.name: - self.parsed.add(os.path.basename(event.filename)) + if event and event.filename != self.name: + for fname in self.extras: + fpath = None + if fname.startswith("/"): + fpath = os.path.abspath(fname) + else: + fpath = \ + os.path.abspath(os.path.join(os.path.dirname(self.name), + fname)) + if fpath == os.path.abspath(event.filename): + self.parsed.add(fname) + break - if self.config.loaded and self.loaded: + if self.loaded: self.logger.info("Reloading Packages plugin") self.pkg_obj.Reload() @@ -81,7 +91,7 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked, return None try: - source = cls(self.cachepath, xsource, self.config) + source = cls(self.cachepath, xsource, self.setup) except SourceInitError: err = sys.exc_info()[1] self.logger.error("Packages: %s" % err) diff --git a/src/lib/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py index 5b0aa4213..edcdcd9f2 100644 --- a/src/lib/Server/Plugins/Packages/Source.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py @@ -36,11 +36,12 @@ class Source(Bcfg2.Server.Plugin.Debuggable): genericrepo_re = re.compile('https?://.*?/([^/]+)/?$') basegroups = [] - def __init__(self, basepath, xsource, config): + def __init__(self, basepath, xsource, setup): Bcfg2.Server.Plugin.Debuggable.__init__(self) self.basepath = basepath self.xsource = xsource - self.config = config + self.setup = setup + self.essentialpkgs = set() try: self.version = xsource.find('Version').text @@ -54,8 +55,9 @@ class Source(Bcfg2.Server.Plugin.Debuggable): self.gpgkeys = [el.text for el in xsource.findall("GPGKey")] + self.essential = xsource.get('essential', 'true').lower() == 'true' self.recommended = xsource.get('recommended', 'false').lower() == 'true' - + self.rawurl = xsource.get('rawurl', '') if self.rawurl and not self.rawurl.endswith("/"): self.rawurl += "/" @@ -112,7 +114,7 @@ class Source(Bcfg2.Server.Plugin.Debuggable): if os.path.exists(self.cachefile): try: self.load_state() - should_read = False + should_read = False except: self.logger.error("Packages: Cachefile %s load failed; " "falling back to file read" % self.cachefile) @@ -270,8 +272,8 @@ class Source(Bcfg2.Server.Plugin.Debuggable): if not found_arch: return False - if self.config.getboolean("global", "magic_groups", - default=True) == False: + if not self.setup.cfp.getboolean("packages", "magic_groups", + default=True): return True else: for group in self.basegroups: diff --git a/src/lib/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py index 9ce462c78..effec1c0e 100644 --- a/src/lib/Server/Plugins/Packages/Yum.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py @@ -15,7 +15,6 @@ from Bcfg2.Bcfg2Py3k import StringIO, cPickle, HTTPError, ConfigParser, file from Bcfg2.Server.Plugins.Packages.Collection import Collection from Bcfg2.Server.Plugins.Packages.Source import SourceInitError, Source, \ fetch_url -from Bcfg2.Server.Plugins.Packages.PackagesConfig import PackagesConfig logger = logging.getLogger(__name__) @@ -50,7 +49,7 @@ PULPSERVER = None PULPCONFIG = None -def _setup_pulp(config): +def _setup_pulp(setup): global PULPSERVER, PULPCONFIG if not has_pulp: msg = "Packages: Cannot create Pulp collection: Pulp libraries not found" @@ -59,8 +58,8 @@ def _setup_pulp(config): if PULPSERVER is None: try: - username = config.get("pulp", "username") - password = config.get("pulp", "password") + username = setup.cfp.get("packages:pulp", "username") + password = setup.cfp.get("packages:pulp", "password") except ConfigParser.NoSectionError: msg = "Packages: No [pulp] section found in Packages/packages.conf" logger.error(msg) @@ -72,7 +71,7 @@ def _setup_pulp(config): PULPCONFIG = ConsumerConfig() serveropts = PULPCONFIG.server - + PULPSERVER = server.PulpServer(serveropts['host'], int(serveropts['port']), serveropts['scheme'], @@ -86,22 +85,17 @@ class YumCollection(Collection): # options that are included in the [yum] section but that should # not be included in the temporary yum.conf we write out option_blacklist = ["use_yum_libraries", "helper"] - + def __init__(self, metadata, sources, basepath, debug=False): Collection.__init__(self, metadata, sources, basepath, debug=debug) self.keypath = os.path.join(self.basepath, "keys") - if len(sources): - self.config = sources[0].config - else: - self.config = PackageConfig('Packages') - if self.use_yum: self.cachefile = os.path.join(self.cachepath, "cache-%s" % self.cachekey) if not os.path.exists(self.cachefile): os.mkdir(self.cachefile) - + self.configdir = os.path.join(self.basepath, "yum") if not os.path.exists(self.configdir): os.mkdir(self.configdir) @@ -109,7 +103,7 @@ class YumCollection(Collection): "%s-yum.conf" % self.cachekey) self.write_config() if has_pulp and self.has_pulp_sources: - _setup_pulp(self.config) + _setup_pulp(self.setup) self._helper = None @@ -132,8 +126,9 @@ class YumCollection(Collection): @property def use_yum(self): - return has_yum and self.config.getboolean("yum", "use_yum_libraries", - default=False) + return has_yum and self.setup.cfp.getboolean("packages:yum", + "use_yum_libraries", + default=False) @property def has_pulp_sources(self): @@ -147,16 +142,16 @@ class YumCollection(Collection): if not os.path.exists(self.cfgfile): yumconf = self.get_config(raw=True) yumconf.add_section("main") - + mainopts = dict(cachedir=self.cachefile, keepcache="0", sslverify="0", debuglevel="0", reposdir="/dev/null") try: - for opt in self.config.options("yum"): + for opt in self.setup.cfp.options("packages:yum"): if opt not in self.option_blacklist: - mainopts[opt] = self.config.get("yum", opt) + mainopts[opt] = self.setup.cfp.get("packages:yum", opt) except ConfigParser.NoSectionError: pass @@ -187,7 +182,7 @@ class YumCollection(Collection): else: rid = 1 reponame = "%s-%d" % (basereponame, rid) - + config.set(reponame, "name", reponame) config.set(reponame, "baseurl", url_map['url']) config.set(reponame, "enabled", "1") @@ -242,12 +237,12 @@ class YumCollection(Collection): for key in needkeys: # figure out the path of the key on the client - keydir = self.config.get("global", "gpg_keypath", - default="/etc/pki/rpm-gpg") + keydir = self.setup.cfp.get("global", "gpg_keypath", + default="/etc/pki/rpm-gpg") remotekey = os.path.join(keydir, os.path.basename(key)) localkey = os.path.join(self.keypath, os.path.basename(key)) kdata = open(localkey).read() - + # copy the key to the client keypath = lxml.etree.Element("BoundPath", name=remotekey, encoding='ascii', @@ -313,12 +308,12 @@ class YumCollection(Collection): # include gpg key data if not has_yum: return - + try: kinfo = yum.misc.getgpgkeyinfo(keydata) version = yum.misc.keyIdToRPMVer(kinfo['keyid']) release = yum.misc.keyIdToRPMVer(kinfo['timestamp']) - + lxml.etree.SubElement(keyentry, 'Instance', version=version, release=release, @@ -398,7 +393,7 @@ class YumCollection(Collection): unknown = set([str(p) for p in result['unknown']]) self.filter_unknown(unknown) - + return packages, unknown def call_helper(self, command, input=None): @@ -424,7 +419,7 @@ class YumCollection(Collection): self.logger.error("Packages: Failed to execute %s: %s" % (" ".join(cmd), err)) return None - + if input: idata = json.dumps(input) (stdout, stderr) = helper.communicate(idata) @@ -456,7 +451,7 @@ class YumCollection(Collection): os.unlink(self.cfgfile) self.write_config() - + if force_update: self.call_helper("clean") @@ -465,13 +460,13 @@ class YumSource(Source): basegroups = ['yum', 'redhat', 'centos', 'fedora'] ptype = 'yum' - def __init__(self, basepath, xsource, config): - Source.__init__(self, basepath, xsource, config) + def __init__(self, basepath, xsource, setup): + Source.__init__(self, basepath, xsource, setup) self.pulp_id = None if has_pulp and xsource.get("pulp_id"): self.pulp_id = xsource.get("pulp_id") - - _setup_pulp(self.config) + + _setup_pulp(self.setup) repoapi = RepositoryAPI() try: self.repo = repoapi.repository(self.pulp_id) @@ -498,7 +493,7 @@ class YumSource(Source): self.rawurl = "%s/%s" % (PULPCONFIG.cds['baseurl'], self.repo['relative_path']) self.arches = [self.repo['arch']] - + if not self.rawurl: self.baseurl = self.url + "%(version)s/%(component)s/%(arch)s/" else: @@ -513,8 +508,9 @@ class YumSource(Source): @property def use_yum(self): - return has_yum and self.config.getboolean("yum", "use_yum_libraries", - default=False) + return has_yum and self.setup.cfp.getboolean("packages:yum", + "use_yum_libraries", + default=False) def save_state(self): if not self.use_yum: @@ -522,7 +518,7 @@ class YumSource(Source): cPickle.dump((self.packages, self.deps, self.provides, self.filemap, self.url_map), cache, 2) cache.close() - + def load_state(self): if not self.use_yum: @@ -538,7 +534,7 @@ class YumSource(Source): usettings = [{'version':self.version, 'component':comp, 'arch':arch} for comp in self.components] - else: # rawurl given + else: # rawurl given usettings = [{'version':self.version, 'component':None, 'arch':arch}] @@ -556,7 +552,7 @@ class YumSource(Source): def _get_urls_from_repodata(self, url, arch): if self.use_yum: return [url] - + rmdurl = '%srepodata/repomd.xml' % url try: repomd = fetch_url(rmdurl) @@ -673,7 +669,7 @@ class YumSource(Source): def get_vpkgs(self, metadata): if self.use_yum: return dict() - + rv = Source.get_vpkgs(self, metadata) for arch, fmdata in list(self.filemap.items()): if arch not in metadata.groups and arch != 'global': diff --git a/src/lib/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py index b04c299a6..72913a60a 100644 --- a/src/lib/Server/Plugins/Packages/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py @@ -10,12 +10,12 @@ import Bcfg2.Server.Plugin from Bcfg2.Bcfg2Py3k import ConfigParser, urlopen from Bcfg2.Server.Plugins.Packages import Collection from Bcfg2.Server.Plugins.Packages.PackagesSources import PackagesSources -from Bcfg2.Server.Plugins.Packages.PackagesConfig import PackagesConfig class Packages(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.StructureValidator, Bcfg2.Server.Plugin.Generator, - Bcfg2.Server.Plugin.Connector): + Bcfg2.Server.Plugin.Connector, + Bcfg2.Server.Plugin.GoalValidator): name = 'Packages' conflicts = ['Pkgmgr'] experimental = True @@ -35,11 +35,9 @@ class Packages(Bcfg2.Server.Plugin.Plugin, # create key directory if needed os.makedirs(self.keypath) - # set up config files - self.config = PackagesConfig(self) self.sources = PackagesSources(os.path.join(self.data, "sources.xml"), self.cachepath, core.fam, self, - self.config) + self.core.setup) def toggle_debug(self): Bcfg2.Server.Plugin.Plugin.toggle_debug(self) @@ -52,7 +50,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, # Things return True try: - return not self.config.getboolean("global", "resolver") + return not self.core.setup.cfp.getboolean("packages", "resolver") except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): return False except ValueError: @@ -60,28 +58,29 @@ class Packages(Bcfg2.Server.Plugin.Plugin, # "disabled", which are not handled according to the # Python docs but appear to be handled properly by # ConfigParser in at least some versions - return self.config.get("global", "resolver", - default="enabled").lower() == "disabled" + return self.core.setup.cfp.get("packages", "resolver", + default="enabled").lower() == "disabled" @property def disableMetaData(self): try: - return not self.config.getboolean("global", "resolver") + return not self.core.setup.cfp.getboolean("packages", "resolver") except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): return False except ValueError: # for historical reasons we also accept "enabled" and # "disabled" - return self.config.get("global", "metadata", - default="enabled").lower() == "disabled" + return self.core.setup.cfp.get("packages", "metadata", + default="enabled").lower() == "disabled" def create_config(self, entry, metadata): """ create yum/apt config for the specified host """ - attrib = {'encoding': 'ascii', - 'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '0644'} + attrib = dict(encoding='ascii', + owner='root', + group='root', + type='file', + perms='0644', + important='true') collection = self._get_collection(metadata) entry.text = collection.get_config() @@ -91,19 +90,23 @@ class Packages(Bcfg2.Server.Plugin.Plugin, def HandleEntry(self, entry, metadata): if entry.tag == 'Package': collection = self._get_collection(metadata) - entry.set('version', 'auto') + entry.set('version', self.core.setup.cfp.get("packages", + "version", + default="auto")) entry.set('type', collection.ptype) elif entry.tag == 'Path': - if (entry.get("name") == self.config.get("global", "yum_config", - default="") or - entry.get("name") == self.config.get("global", "apt_config", - default="")): + if (entry.get("name") == self.core.setup.cfp.get("packages", + "yum_config", + default="") or + entry.get("name") == self.core.setup.cfp.get("packages", + "apt_config", + default="")): self.create_config(entry, metadata) def HandlesEntry(self, entry, metadata): if entry.tag == 'Package': - if self.config.getboolean("global", "magic_groups", - default=True) == True: + if self.core.setup.cfp.getboolean("packages", "magic_groups", + default=True): collection = self._get_collection(metadata) if collection.magic_groups_match(): return True @@ -111,10 +114,12 @@ class Packages(Bcfg2.Server.Plugin.Plugin, return True elif entry.tag == 'Path': # managed entries for yum/apt configs - if (entry.get("name") == self.config.get("global", "yum_config", - default="") or - entry.get("name") == self.config.get("global", "apt_config", - default="")): + if (entry.get("name") == self.core.setup.cfp.get("packages", + "yum_config", + default="") or + entry.get("name") == self.core.setup.cfp.get("packages", + "apt_config", + default="")): return True return False @@ -147,6 +152,8 @@ class Packages(Bcfg2.Server.Plugin.Plugin, initial = set() # base is the set of initial packages with groups expanded base = set() + # essential pkgs are those marked as such by the distribution + essential = collection.get_essential() to_remove = [] for struct in structures: for pkg in struct.xpath('//Package | //BoundPackage'): @@ -168,7 +175,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin, else: self.logger.error("Packages: Malformed Package: %s" % lxml.etree.tostring(pkg)) - base.update(initial) + base.update(initial | essential) for el in to_remove: el.getparent().remove(el) @@ -182,8 +189,10 @@ class Packages(Bcfg2.Server.Plugin.Plugin, newpkgs.sort() for pkg in newpkgs: lxml.etree.SubElement(independent, 'BoundPackage', name=pkg, - version='auto', type=collection.ptype, - origin='Packages') + version=self.core.setup.cfp.get("packages", + "version", + default="auto"), + type=collection.ptype, origin='Packages') def Refresh(self): '''Packages.Refresh() => True|False\nReload configuration @@ -265,3 +274,11 @@ class Packages(Bcfg2.Server.Plugin.Plugin, def get_additional_data(self, metadata): collection = self._get_collection(metadata) return dict(sources=collection.get_additional_data()) + + def validate_goals(self, metadata, _): + """ we abuse the GoalValidator plugin since validate_goals() + is the very last thing called during a client config run. so + we use this to clear the collection cache for this client, + which must persist only the duration of a client run """ + if metadata.hostname in Collection.clients: + del Collection.clients[metadata.hostname] diff --git a/src/lib/Server/Plugins/Pkgmgr.py b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py index bc11bfdcf..e9254cdcc 100644 --- a/src/lib/Server/Plugins/Pkgmgr.py +++ b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py @@ -1,5 +1,4 @@ '''This module implements a package management scheme for all images''' -__revision__ = '$Revision$' import logging import re @@ -135,7 +134,6 @@ class PkgSrc(Bcfg2.Server.Plugin.XMLSrc): class Pkgmgr(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles package assignments.""" name = 'Pkgmgr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = PkgSrc __element__ = 'Package' diff --git a/src/lib/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 2c24d6cc4..22cacde55 100644 --- a/src/lib/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -53,10 +53,10 @@ class ProbeData(object): def __str__(self): return str(self.data) - + def __repr__(self): return repr(self.data) - + def __getattr__(self, name): """ make ProbeData act like a str object """ return getattr(self.data, name) @@ -184,7 +184,6 @@ class Probes(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): """A plugin to gather information from a client machine.""" name = 'Probes' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py index 76945b3a0..680881858 100644 --- a/src/lib/Server/Plugins/Properties.py +++ b/src/lib/Bcfg2/Server/Plugins/Properties.py @@ -60,7 +60,6 @@ class Properties(Bcfg2.Server.Plugin.Plugin, files into client metadata instances. """ name = 'Properties' - version = '$Revision$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) diff --git a/src/lib/Server/Plugins/Rules.py b/src/lib/Bcfg2/Server/Plugins/Rules.py index c66276179..e77d08653 100644 --- a/src/lib/Server/Plugins/Rules.py +++ b/src/lib/Bcfg2/Server/Plugins/Rules.py @@ -1,21 +1,15 @@ """This generator provides rule-based entry mappings.""" -__revision__ = '$Revision$' import re import Bcfg2.Server.Plugin -class RulesConfig(Bcfg2.Server.Plugin.SimpleConfig): - _required = False - class Rules(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles service assignments.""" name = 'Rules' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): Bcfg2.Server.Plugin.PrioDir.__init__(self, core, datastore) - self.config = RulesConfig(self) self._regex_cache = dict() def HandlesEntry(self, entry, metadata): @@ -52,6 +46,6 @@ class Rules(Bcfg2.Server.Plugin.PrioDir): if self._regex_cache[rule].match(entry.get('name')): return True return False - + def _regex_enabled(self): - return self.config.getboolean("rules", "regex", default=False) + return self.core.setup.cfp.getboolean("rules", "regex", default=False) diff --git a/src/lib/Server/Plugins/SGenshi.py b/src/lib/Bcfg2/Server/Plugins/SGenshi.py index ae2623d29..12c125c62 100644 --- a/src/lib/Server/Plugins/SGenshi.py +++ b/src/lib/Bcfg2/Server/Plugins/SGenshi.py @@ -1,5 +1,4 @@ '''This module implements a templating generator based on Genshi''' -__revision__ = '$Revision$' import genshi.input import genshi.template @@ -85,7 +84,6 @@ class SGenshi(SGenshiEntrySet, Bcfg2.Server.Plugin.Structure): """The SGenshi plugin provides templated structures.""" name = 'SGenshi' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' deprecated = True diff --git a/src/lib/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py index eb91bea39..a1a29727f 100644 --- a/src/lib/Server/Plugins/SSHbase.py +++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py @@ -1,5 +1,4 @@ -'''This module manages ssh key files for bcfg2''' -__revision__ = '$Revision$' +"""This module manages ssh key files for bcfg2""" import binascii import re @@ -20,10 +19,15 @@ logger = logging.getLogger(__name__) class KeyData(Bcfg2.Server.Plugin.SpecificData): def __init__(self, name, specific, encoding): - Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific, + Bcfg2.Server.Plugin.SpecificData.__init__(self, + name, + specific, encoding) self.encoding = encoding - + + def __lt__(self, other): + return self.name < other.name + def bind_entry(self, entry, metadata): entry.set('type', 'file') if entry.get('encoding') == 'base64': @@ -99,7 +103,6 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, """ name = 'SSHbase' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' keypatterns = ["ssh_host_dsa_key", @@ -148,7 +151,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, for s in list(self.static.values())] mquery = self.core.metadata.query - + # build hostname cache names = dict() for cmeta in mquery.all(): @@ -206,15 +209,15 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, # have no clients yet. don't warn about # this. continue - + if key not in self.badnames: self.badnames[key] = True self.logger.info("Ignoring key for unknown %s %s" % (ktype, key)) continue - + skn.append("%s %s" % (','.join(hostnames), - entry.data.decode().rstrip())) + entry.data.rstrip())) self.__skn = "\n".join(skn) + "\n" return self.__skn @@ -230,7 +233,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, action = event.code2str() if action == "endExist" or event.filename == self.data: return - + for entry in list(self.entries.values()): if entry.specific.match(event.filename): entry.handle_event(event) @@ -325,8 +328,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, pass hostkeys.sort() for hostkey in hostkeys: - entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" % ( - hostkey.data.decode()) + entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" % \ + (hostkey.data) self.entries[entry.get('name')].bind_info_to_entry(entry, metadata) def build_hk(self, entry, metadata): @@ -371,7 +374,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, else: self.logger.error("Unknown key filename: %s" % filename) return - + fileloc = "%s/%s" % (self.data, hostkey) publoc = self.data + '/' + ".".join([hostkey.split('.')[0], 'pub', "H_%s" % client]) diff --git a/src/lib/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py index e5bc38fba..1091fc2c8 100644 --- a/src/lib/Server/Plugins/SSLCA.py +++ b/src/lib/Bcfg2/Server/Plugins/SSLCA.py @@ -16,7 +16,6 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): management of ssl certificates and their keys. """ name = 'SSLCA' - __version__ = '$Id:$' __author__ = 'g.hagger@gmail.com' __child__ = Bcfg2.Server.Plugin.FileBacked key_specs = {} diff --git a/src/lib/Server/Plugins/Snapshots.py b/src/lib/Bcfg2/Server/Plugins/Snapshots.py index aeb3b9f74..aeb3b9f74 100644 --- a/src/lib/Server/Plugins/Snapshots.py +++ b/src/lib/Bcfg2/Server/Plugins/Snapshots.py diff --git a/src/lib/Server/Plugins/Statistics.py b/src/lib/Bcfg2/Server/Plugins/Statistics.py index 9dbbeec28..265ef95a8 100644 --- a/src/lib/Server/Plugins/Statistics.py +++ b/src/lib/Bcfg2/Server/Plugins/Statistics.py @@ -1,5 +1,4 @@ '''This file manages the statistics collected by the BCFG2 Server''' -__revision__ = '$Revision$' import binascii import copy @@ -117,7 +116,6 @@ class Statistics(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.ThreadedStatistics, Bcfg2.Server.Plugin.PullSource): name = 'Statistics' - __version__ = '$Id$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) diff --git a/src/lib/Server/Plugins/Svcmgr.py b/src/lib/Bcfg2/Server/Plugins/Svcmgr.py index 6d25c1a6d..f4232ad5c 100644 --- a/src/lib/Server/Plugins/Svcmgr.py +++ b/src/lib/Bcfg2/Server/Plugins/Svcmgr.py @@ -1,5 +1,4 @@ """This generator provides service mappings.""" -__revision__ = '$Revision$' import Bcfg2.Server.Plugin @@ -7,6 +6,5 @@ import Bcfg2.Server.Plugin class Svcmgr(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles service assignments.""" name = 'Svcmgr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' deprecated = True diff --git a/src/lib/Server/Plugins/Svn.py b/src/lib/Bcfg2/Server/Plugins/Svn.py index 9fd6f1051..ae43388ea 100644 --- a/src/lib/Server/Plugins/Svn.py +++ b/src/lib/Bcfg2/Server/Plugins/Svn.py @@ -12,7 +12,6 @@ class Svn(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Svn is a version plugin for dealing with Bcfg2 repos.""" name = 'Svn' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Svn2.py b/src/lib/Bcfg2/Server/Plugins/Svn2.py index b8a8e6b7e..e4df9574f 100644 --- a/src/lib/Server/Plugins/Svn2.py +++ b/src/lib/Bcfg2/Server/Plugins/Svn2.py @@ -9,7 +9,6 @@ class Svn2(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Svn is a version plugin for dealing with Bcfg2 repos.""" name = 'Svn2' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' conflicts = ['Svn'] @@ -72,7 +71,8 @@ class Svn2(Bcfg2.Server.Plugin.Plugin, self.revision = self.client.update(self.datastore, recurse=True)[0] self.logger.info("Svn2: Commited changes. At %s" % self.revision.number) - except Exception, err: + except Exception: + err = sys.exc_info()[1] # try to be smart about the error we got back details = None if "callback_ssl_server_trust_prompt" in str(err): diff --git a/src/lib/Server/Plugins/TCheetah.py b/src/lib/Bcfg2/Server/Plugins/TCheetah.py index 49be88881..8879fdef1 100644 --- a/src/lib/Server/Plugins/TCheetah.py +++ b/src/lib/Bcfg2/Server/Plugins/TCheetah.py @@ -1,5 +1,4 @@ '''This module implements a templating generator based on Cheetah''' -__revision__ = '$Revision$' import binascii import logging @@ -76,7 +75,6 @@ class TemplateFile: class TCheetah(Bcfg2.Server.Plugin.GroupSpool): """The TCheetah generator implements a templating mechanism for configuration files.""" name = 'TCheetah' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filename_pattern = 'template' es_child_cls = TemplateFile diff --git a/src/lib/Server/Plugins/TGenshi.py b/src/lib/Bcfg2/Server/Plugins/TGenshi.py index 072f5cd7b..c4dd40614 100644 --- a/src/lib/Server/Plugins/TGenshi.py +++ b/src/lib/Bcfg2/Server/Plugins/TGenshi.py @@ -1,5 +1,4 @@ """This module implements a templating generator based on Genshi.""" -__revision__ = '$Revision$' import binascii import logging @@ -115,11 +114,13 @@ class TemplateFile: if entry.text == '': entry.set('empty', 'true') except TemplateError: + err = sys.exc_info()[1] logger.exception('Genshi template error') - raise Bcfg2.Server.Plugin.PluginExecutionError + raise Bcfg2.Server.Plugin.PluginExecutionError('Genshi template error: %s' % err) except AttributeError: + err = sys.exc_info()[1] logger.exception('Genshi template loading error') - raise Bcfg2.Server.Plugin.PluginExecutionError + raise Bcfg2.Server.Plugin.PluginExecutionError('Genshi template loading error: %s' % err) class TGenshi(Bcfg2.Server.Plugin.GroupSpool): @@ -129,7 +130,6 @@ class TGenshi(Bcfg2.Server.Plugin.GroupSpool): """ name = 'TGenshi' - __version__ = '$Id$' __author__ = 'jeff@ocjtech.us' filename_pattern = 'template\.(txt|newtxt|xml)' es_child_cls = TemplateFile diff --git a/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py new file mode 100644 index 000000000..2c0ee03e0 --- /dev/null +++ b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py @@ -0,0 +1,83 @@ +import re +import imp +import sys +import logging +import Bcfg2.Server.Plugin + +logger = logging.getLogger(__name__) + +class HelperModule(Bcfg2.Server.Plugin.SpecificData): + _module_name_re = re.compile(r'([^/]+?)\.py') + + def __init__(self, name, specific, encoding): + Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific, + encoding) + match = self._module_name_re.search(self.name) + if match: + self._module_name = match.group(1) + else: + self._module_name = name + self._attrs = [] + + def handle_event(self, event): + Bcfg2.Server.Plugin.SpecificData.handle_event(self, event) + try: + module = imp.load_source(self._module_name, self.name) + except: + err = sys.exc_info()[1] + logger.error("TemplateHelper: Failed to import %s: %s" % + (self.name, err)) + return + + if not hasattr(module, "__export__"): + logger.error("TemplateHelper: %s has no __export__ list" % + self.name) + return + + for sym in module.__export__: + if sym not in self._attrs and hasattr(self, sym): + logger.warning("TemplateHelper: %s: %s is a reserved keyword, " + "skipping export" % (self.name, sym)) + setattr(self, sym, getattr(module, sym)) + # remove old exports + for sym in set(self._attrs) - set(module.__export__): + delattr(self, sym) + + self._attrs = module.__export__ + + +class HelperSet(Bcfg2.Server.Plugin.EntrySet): + ignore = re.compile("^(\.#.*|.*~|\\..*\\.(sw[px])|.*\.py[co])$") + + def __init__(self, path, fam, encoding, plugin_name): + fpattern = '[0-9A-Za-z_\-]+\.py' + self.plugin_name = plugin_name + Bcfg2.Server.Plugin.EntrySet.__init__(self, fpattern, path, + HelperModule, encoding) + fam.AddMonitor(path, self) + + def HandleEvent(self, event): + if (event.filename != self.path and + not self.ignore.match(event.filename)): + return self.handle_event(event) + + +class TemplateHelper(Bcfg2.Server.Plugin.Plugin, + Bcfg2.Server.Plugin.Connector): + """ A plugin to provide helper classes and functions to templates """ + name = 'TemplateHelper' + __author__ = 'chris.a.st.pierre@gmail.com' + + def __init__(self, core, datastore): + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) + Bcfg2.Server.Plugin.Connector.__init__(self) + + try: + self.helpers = HelperSet(self.data, core.fam, core.encoding, + self.name) + except: + raise Bcfg2.Server.Plugin.PluginInitError + + def get_additional_data(self, metadata): + return dict([(h._module_name, h) + for h in list(self.helpers.entries.values())]) diff --git a/src/lib/Server/Plugins/Trigger.py b/src/lib/Bcfg2/Server/Plugins/Trigger.py index eb3310a4e..b0d21545c 100644 --- a/src/lib/Server/Plugins/Trigger.py +++ b/src/lib/Bcfg2/Server/Plugins/Trigger.py @@ -17,7 +17,6 @@ class Trigger(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Statistics): """Trigger is a plugin that calls external scripts (on the server).""" name = 'Trigger' - __version__ = '$Id' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/__init__.py b/src/lib/Bcfg2/Server/Plugins/__init__.py index c69c37452..f9f1b4e52 100644 --- a/src/lib/Server/Plugins/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/__init__.py @@ -1,5 +1,4 @@ """Imports for Bcfg2.Server.Plugins.""" -__revision__ = '$Revision$' __all__ = [ 'Account', @@ -8,7 +7,7 @@ __all__ = [ 'Bzr', 'Cfg', 'Cvs', - 'Darcs', + 'Darcs', 'Decisions', 'Fossil', 'Git', diff --git a/src/lib/Server/Reports/__init__.py b/src/lib/Bcfg2/Server/Reports/__init__.py index bdf908f4a..bdf908f4a 100644 --- a/src/lib/Server/Reports/__init__.py +++ b/src/lib/Bcfg2/Server/Reports/__init__.py diff --git a/src/lib/Server/Reports/backends.py b/src/lib/Bcfg2/Server/Reports/backends.py index 85241932f..85241932f 100644 --- a/src/lib/Server/Reports/backends.py +++ b/src/lib/Bcfg2/Server/Reports/backends.py diff --git a/src/lib/Server/Reports/importscript.py b/src/lib/Bcfg2/Server/Reports/importscript.py index 7dfac6fae..16df86a9b 100755 --- a/src/lib/Server/Reports/importscript.py +++ b/src/lib/Bcfg2/Server/Reports/importscript.py @@ -3,7 +3,6 @@ Imports statistics.xml and clients.xml files in to database backend for new statistics engine """ -__revision__ = '$Revision$' import binascii import os @@ -41,6 +40,7 @@ from Bcfg2.Bcfg2Py3k import ConfigParser def build_reason_kwargs(r_ent, encoding, logger): binary_file = False sensitive_file = False + unpruned_entries = '' if r_ent.get('sensitive') in ['true', 'True']: sensitive_file = True rc_diff = '' @@ -58,6 +58,10 @@ def build_reason_kwargs(r_ent, encoding, logger): rc_diff = r_ent.get('current_diff') else: rc_diff = '' + # detect unmanaged entries in pruned directories + if r_ent.get('prune', 'false') == 'true' and r_ent.get('qtest'): + unpruned_elist = [e.get('path') for e in r_ent.findall('Prune')] + unpruned_entries = "\n".join(unpruned_elist) if not binary_file: try: rc_diff = rc_diff.decode(encoding) @@ -79,7 +83,8 @@ def build_reason_kwargs(r_ent, encoding, logger): current_exists=r_ent.get('current_exists', default="True").capitalize() == "True", current_diff=rc_diff, is_binary=binary_file, - is_sensitive=sensitive_file) + is_sensitive=sensitive_file, + unpruned=unpruned_entries) def load_stats(cdata, sdata, encoding, vlevel, logger, quick=False, location=''): @@ -118,8 +123,6 @@ def load_stats(cdata, sdata, encoding, vlevel, logger, quick=False, location='') default="unknown"), repo_rev_code=statistics.get('revision', default="unknown"), - client_version=statistics.get('client_version', - default="unknown"), goodcount=statistics.get('good', default="0"), totalcount=statistics.get('total', diff --git a/src/lib/Server/Reports/manage.py b/src/lib/Bcfg2/Server/Reports/manage.py index 858bddeca..858bddeca 100755 --- a/src/lib/Server/Reports/manage.py +++ b/src/lib/Bcfg2/Server/Reports/manage.py diff --git a/src/lib/Server/Reports/nisauth.py b/src/lib/Bcfg2/Server/Reports/nisauth.py index 6fc346f1e..b3e37113b 100644 --- a/src/lib/Server/Reports/nisauth.py +++ b/src/lib/Bcfg2/Server/Reports/nisauth.py @@ -4,8 +4,6 @@ from Bcfg2.Server.Reports.settings import AUTHORIZED_GROUP """Checks with NIS to see if the current user is in the support group""" -__revision__ = "$Revision: $" - class NISAUTHError(Exception): """NISAUTHError is raised when somehting goes boom.""" diff --git a/src/lib/Server/Reports/reports/__init__.py b/src/lib/Bcfg2/Server/Reports/reports/__init__.py index ccdce8943..ccdce8943 100644 --- a/src/lib/Server/Reports/reports/__init__.py +++ b/src/lib/Bcfg2/Server/Reports/reports/__init__.py diff --git a/src/lib/Server/Reports/reports/fixtures/initial_version.xml b/src/lib/Bcfg2/Server/Reports/reports/fixtures/initial_version.xml index 919265d48..bde236989 100644 --- a/src/lib/Server/Reports/reports/fixtures/initial_version.xml +++ b/src/lib/Bcfg2/Server/Reports/reports/fixtures/initial_version.xml @@ -36,4 +36,8 @@ <field type='IntegerField' name='version'>18</field> <field type='DateTimeField' name='updated'>2011-06-30 00:00:00</field> </object> + <object pk="8" model="reports.internaldatabaseversion"> + <field type='IntegerField' name='version'>19</field> + <field type='DateTimeField' name='updated'>2012-03-28 00:00:00</field> + </object> </django-objects> diff --git a/src/lib/Server/Reports/reports/models.py b/src/lib/Bcfg2/Server/Reports/reports/models.py index 870239641..9a71fb16a 100644 --- a/src/lib/Server/Reports/reports/models.py +++ b/src/lib/Bcfg2/Server/Reports/reports/models.py @@ -1,5 +1,14 @@ """Django models for Bcfg2 reports.""" -from django.db import models +import sys + +from django.core.exceptions import ImproperlyConfigured +try: + from django.db import models +except ImproperlyConfigured: + e = sys.exc_info()[1] + print("Reports: unable to import django models: %s" % e) + sys.exit(1) + from django.db import connection, transaction from django.db.models import Q from datetime import datetime, timedelta @@ -145,7 +154,7 @@ class InteractiveManager(models.Manager): cursor.execute(sql) return [item[0] for item in cursor.fetchall()] except: - '''FIXME - really need some error hadling''' + '''FIXME - really need some error handling''' pass return [] @@ -156,7 +165,6 @@ class Interaction(models.Model): timestamp = models.DateTimeField() # Timestamp for this record state = models.CharField(max_length=32) # good/bad/modified/etc repo_rev_code = models.CharField(max_length=64) # repo revision at time of interaction - client_version = models.CharField(max_length=32) # Client Version goodcount = models.IntegerField() # of good config-items totalcount = models.IntegerField() # of total config-items server = models.CharField(max_length=256) # Name of the server used for the interaction @@ -278,6 +286,7 @@ class Reason(models.Model): current_diff = models.TextField(max_length=1280, blank=True) is_binary = models.BooleanField(default=False) is_sensitive = models.BooleanField(default=False) + unpruned = models.TextField(max_length=1280, blank=True) def _str_(self): return "Reason" diff --git a/src/lib/Server/Reports/reports/sql/client.sql b/src/lib/Bcfg2/Server/Reports/reports/sql/client.sql index 8c63754c9..28e785450 100644 --- a/src/lib/Server/Reports/reports/sql/client.sql +++ b/src/lib/Bcfg2/Server/Reports/reports/sql/client.sql @@ -1,9 +1,7 @@ CREATE VIEW reports_current_interactions AS SELECT x.client_id AS client_id, reports_interaction.id AS interaction_id FROM (select client_id, MAX(timestamp) as timer FROM reports_interaction GROUP BY client_id) x, reports_interaction WHERE reports_interaction.client_id = x.client_id AND reports_interaction.timestamp = x.timer; create index reports_interaction_client_id on reports_interaction (client_id); -create index reports_extra_interactions_client_id on reports_extra_interactions(interaction_id); -create index reports_modified_interactions_client_id on reports_modified_interactions(interaction_id); create index reports_client_current_interaction_id on reports_client (current_interaction_id); create index reports_performance_interaction_performance_id on reports_performance_interaction (performance_id); create index reports_interaction_timestamp on reports_interaction (timestamp); -create index reports_performance_interation_interaction_id on reports_performance_interaction (interaction_id);
\ No newline at end of file +create index reports_performance_interation_interaction_id on reports_performance_interaction (interaction_id); diff --git a/src/lib/Server/Reports/reports/templates/404.html b/src/lib/Bcfg2/Server/Reports/reports/templates/404.html index 168bd9fec..168bd9fec 100644 --- a/src/lib/Server/Reports/reports/templates/404.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/404.html diff --git a/src/lib/Server/Reports/reports/templates/base-timeview.html b/src/lib/Bcfg2/Server/Reports/reports/templates/base-timeview.html index 842de36f0..842de36f0 100644 --- a/src/lib/Server/Reports/reports/templates/base-timeview.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/base-timeview.html diff --git a/src/lib/Server/Reports/reports/templates/base.html b/src/lib/Bcfg2/Server/Reports/reports/templates/base.html index f541c0d2b..f541c0d2b 100644 --- a/src/lib/Server/Reports/reports/templates/base.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/base.html diff --git a/src/lib/Server/Reports/reports/templates/clients/detail.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/detail.html index dd4295f21..dd4295f21 100644 --- a/src/lib/Server/Reports/reports/templates/clients/detail.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/detail.html diff --git a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/detailed-list.html index 0c1fae8d5..84ac71d92 100644 --- a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/detailed-list.html @@ -23,7 +23,7 @@ <tr class='{% cycle listview,listview_alt %}'> <td class='left_column'><a href='{% url Bcfg2.Server.Reports.reports.views.client_detail hostname=entry.client.name, pk=entry.id %}'>{{ entry.client.name }}</a></td> <td class='right_column' style='width:75px'><a href='{% add_url_filter state=entry.state %}' - {% ifequal entry.state 'dirty' %}class='dirty-lineitem'{% endifequal %}>{{ entry.state }}</a></td> + class='{{entry|determine_client_state}}'>{{ entry.state }}</a></td> <td class='right_column_narrow'>{{ entry.goodcount }}</td> <td class='right_column_narrow'>{{ entry.bad_entry_count }}</td> <td class='right_column_narrow'>{{ entry.modified_entry_count }}</td> diff --git a/src/lib/Server/Reports/reports/templates/clients/history.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/history.html index 01d4ec2f4..01d4ec2f4 100644 --- a/src/lib/Server/Reports/reports/templates/clients/history.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/history.html diff --git a/src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html new file mode 100644 index 000000000..134e237d6 --- /dev/null +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html @@ -0,0 +1,34 @@ +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} + +{% block extra_header_info %} +{% endblock%} + +{% block title %}Bcfg2 - Client Grid View{% endblock %} + +{% block pagebanner %}Clients - Grid View{% endblock %} + +{% block content %} +{% if inter_list %} + <table class='grid-view' align='center'> + {% for inter in inter_list %} + {% if forloop.first %}<tr>{% endif %} + <td class='{{ inter|determine_client_state }}'> + <a href="{% spaceless %} + {% if not timestamp %} + {% url reports_client_detail inter.client.name %} + {% else %} + {% url reports_client_detail_pk inter.client.name,inter.id %} + {% endif %} + {% endspaceless %}">{{ inter.client.name }}</a> + </td> + {% if forloop.last %} + </tr> + {% else %} + {% if forloop.counter|divisibleby:"4" %}</tr><tr>{% endif %} + {% endif %} + {% endfor %} + </table> +{% else %}<p>No client records are available.</p> +{% endif %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/manage.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/manage.html index 5725ae577..5725ae577 100644 --- a/src/lib/Server/Reports/reports/templates/clients/manage.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/manage.html diff --git a/src/lib/Server/Reports/reports/templates/config_items/item.html b/src/lib/Bcfg2/Server/Reports/reports/templates/config_items/item.html index cc99ef503..42c3e8349 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/item.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/config_items/item.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load split %} {% load syntax_coloring %} @@ -91,6 +92,20 @@ div.entry_list h3 { </div> {% endif %} + <!-- display extra directory entries --> + {% if item.reason.unpruned != '' %} + <div class='entry_list'> + <div class='entry_list_head'> + <h3>Extra entries found</h3> + </div> + <table class='entry_list' cellpadding='3'> + {% for unpruned_item in item.reason.unpruned|split %} + <tr><td>{{ unpruned_item }}</td></tr> + {% endfor %} + </table> + </div> + {% endif %} + <div class='entry_list'> <div class='entry_list_head'> diff --git a/src/lib/Server/Reports/reports/templates/config_items/listing.html b/src/lib/Bcfg2/Server/Reports/reports/templates/config_items/listing.html index 9b1026a08..9b1026a08 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/listing.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/config_items/listing.html diff --git a/src/lib/Server/Reports/reports/templates/displays/summary.html b/src/lib/Bcfg2/Server/Reports/reports/templates/displays/summary.html index b9847cf96..b9847cf96 100644 --- a/src/lib/Server/Reports/reports/templates/displays/summary.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/displays/summary.html diff --git a/src/lib/Server/Reports/reports/templates/displays/timing.html b/src/lib/Bcfg2/Server/Reports/reports/templates/displays/timing.html index 47accb2cb..ff775ded5 100644 --- a/src/lib/Server/Reports/reports/templates/displays/timing.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/displays/timing.html @@ -20,7 +20,7 @@ <td>Install</td> <td>Config</td> <td>Total</td> - </tr> + </tr> {% for metric in metrics|dictsort:"name" %} <tr class='{% cycle listview,listview_alt %}'> <td><a style='font-size: 100%' diff --git a/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/filter_bar.html index 6b57baf6a..6fbe585ab 100644 --- a/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/filter_bar.html @@ -2,7 +2,7 @@ {% if filters %} {% for filter, filter_url in filters %} {% if forloop.first %} - <div class="filter_bar">Active filters (click to remove): + <div class="filter_bar">Active filters (click to remove): {% endif %} <a href='{{ filter_url }}'>{{ filter|capfirst }}</a>{% if not forloop.last %}, {% endif %} {% if forloop.last %} diff --git a/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/interaction_list.inc index 8f2dec1dc..6fe7e6547 100644 --- a/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/interaction_list.inc @@ -20,7 +20,7 @@ <td class='right_column_wide'><a href='{% add_url_filter hostname=entry.client.name %}'>{{ entry.client.name }}</a></td> {% endif %} <td class='right_column' style='width:75px'><a href='{% add_url_filter state=entry.state %}' - {% ifequal entry.state 'dirty' %}class='dirty-lineitem'{% endifequal %}>{{ entry.state }}</a></td> + class='{{entry|determine_client_state}}'>{{ entry.state }}</a></td> <td class='right_column_narrow'>{{ entry.goodcount }}</td> <td class='right_column_narrow'>{{ entry.bad_entry_count }}</td> <td class='right_column_narrow'>{{ entry.modified_entry_count }}</td> diff --git a/src/lib/Server/Reports/reports/templates/widgets/page_bar.html b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/page_bar.html index aa0def83e..aa0def83e 100644 --- a/src/lib/Server/Reports/reports/templates/widgets/page_bar.html +++ b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/page_bar.html diff --git a/src/lib/Server/Reports/reports/templatetags/__init__.py b/src/lib/Bcfg2/Server/Reports/reports/templatetags/__init__.py index e69de29bb..e69de29bb 100644 --- a/src/lib/Server/Reports/reports/templatetags/__init__.py +++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/__init__.py diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Bcfg2/Server/Reports/reports/templatetags/bcfg2_tags.py index 629984f26..ac63cda3e 100644 --- a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py +++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -1,5 +1,8 @@ +import sys + from django import template -from django.core.urlresolvers import resolve, reverse, Resolver404, NoReverseMatch +from django.core.urlresolvers import resolve, reverse, \ + Resolver404, NoReverseMatch from django.utils.encoding import smart_unicode, smart_str from datetime import datetime, timedelta from Bcfg2.Server.Reports.utils import filter_list @@ -8,13 +11,14 @@ register = template.Library() __PAGE_NAV_LIMITS__ = (10, 25, 50, 100) + @register.inclusion_tag('widgets/page_bar.html', takes_context=True) def page_navigator(context): """ Creates paginated links. - Expects the context to be a RequestContext and views.prepare_paginated_list() - to have populated page information. + Expects the context to be a RequestContext and + views.prepare_paginated_list() to have populated page information. """ fragment = dict() try: @@ -30,70 +34,73 @@ def page_navigator(context): return {} try: - view, args, kwargs = resolve(path) - current_page = int(kwargs.get('page_number',1)) - fragment['current_page'] = current_page - fragment['page_number'] = current_page - fragment['total_pages'] = total_pages - fragment['records_per_page'] = records_per_page - if current_page > 1: - kwargs['page_number'] = current_page - 1 - fragment['prev_page'] = reverse(view, args=args, kwargs=kwargs) - if current_page < total_pages: - kwargs['page_number'] = current_page + 1 - fragment['next_page'] = reverse(view, args=args, kwargs=kwargs) - - view_range = 5 - if total_pages > view_range: - pager_start = current_page - 2 - pager_end = current_page + 2 - if pager_start < 1: - pager_end += (1 - pager_start) - pager_start = 1 - if pager_end > total_pages: - pager_start -= (pager_end - total_pages) - pager_end = total_pages - else: - pager_start = 1 - pager_end = total_pages - - if pager_start > 1: - kwargs['page_number'] = 1 - fragment['first_page'] = reverse(view, args=args, kwargs=kwargs) - if pager_end < total_pages: - kwargs['page_number'] = total_pages - fragment['last_page'] = reverse(view, args=args, kwargs=kwargs) - - pager = [] - for page in range(pager_start, int(pager_end) + 1): - kwargs['page_number'] = page - pager.append( (page, reverse(view, args=args, kwargs=kwargs)) ) - - kwargs['page_number'] = 1 - page_limits = [] - for limit in __PAGE_NAV_LIMITS__: - kwargs['page_limit'] = limit - page_limits.append( (limit, reverse(view, args=args, kwargs=kwargs)) ) - # resolver doesn't like this - del kwargs['page_number'] - del kwargs['page_limit'] - page_limits.append( ('all', reverse(view, args=args, kwargs=kwargs) + "|all") ) - - fragment['pager'] = pager - fragment['page_limits'] = page_limits - + view, args, kwargs = resolve(path) + current_page = int(kwargs.get('page_number', 1)) + fragment['current_page'] = current_page + fragment['page_number'] = current_page + fragment['total_pages'] = total_pages + fragment['records_per_page'] = records_per_page + if current_page > 1: + kwargs['page_number'] = current_page - 1 + fragment['prev_page'] = reverse(view, args=args, kwargs=kwargs) + if current_page < total_pages: + kwargs['page_number'] = current_page + 1 + fragment['next_page'] = reverse(view, args=args, kwargs=kwargs) + + view_range = 5 + if total_pages > view_range: + pager_start = current_page - 2 + pager_end = current_page + 2 + if pager_start < 1: + pager_end += (1 - pager_start) + pager_start = 1 + if pager_end > total_pages: + pager_start -= (pager_end - total_pages) + pager_end = total_pages + else: + pager_start = 1 + pager_end = total_pages + + if pager_start > 1: + kwargs['page_number'] = 1 + fragment['first_page'] = reverse(view, args=args, kwargs=kwargs) + if pager_end < total_pages: + kwargs['page_number'] = total_pages + fragment['last_page'] = reverse(view, args=args, kwargs=kwargs) + + pager = [] + for page in range(pager_start, int(pager_end) + 1): + kwargs['page_number'] = page + pager.append((page, reverse(view, args=args, kwargs=kwargs))) + + kwargs['page_number'] = 1 + page_limits = [] + for limit in __PAGE_NAV_LIMITS__: + kwargs['page_limit'] = limit + page_limits.append((limit, + reverse(view, args=args, kwargs=kwargs))) + # resolver doesn't like this + del kwargs['page_number'] + del kwargs['page_limit'] + page_limits.append(('all', + reverse(view, args=args, kwargs=kwargs) + "|all")) + + fragment['pager'] = pager + fragment['page_limits'] = page_limits + except Resolver404: - path = "404" + path = "404" except NoReverseMatch: - nr = sys.exc_info()[1] - path = "NoReverseMatch: %s" % nr + nr = sys.exc_info()[1] + path = "NoReverseMatch: %s" % nr except ValueError: - path = "ValueError" + path = "ValueError" #FIXME - Handle these fragment['path'] = path return fragment + @register.inclusion_tag('widgets/filter_bar.html', takes_context=True) def filter_navigator(context): try: @@ -111,13 +118,15 @@ def filter_navigator(context): if filter in kwargs: myargs = kwargs.copy() del myargs[filter] - filters.append( (filter, reverse(view, args=args, kwargs=myargs) ) ) - filters.sort(lambda x,y: cmp(x[0], y[0])) - return { 'filters': filters } + filters.append((filter, + reverse(view, args=args, kwargs=myargs))) + filters.sort(lambda x, y: cmp(x[0], y[0])) + return {'filters': filters} except (Resolver404, NoReverseMatch, ValueError, KeyError): pass return dict() + def _subtract_or_na(mdict, x, y): """ Shortcut for build_metric_list @@ -127,50 +136,54 @@ def _subtract_or_na(mdict, x, y): except: return "n/a" + @register.filter def build_metric_list(mdict): """ Create a list of metric table entries - Moving this here it simplify the view. Should really handle the case where these - are missing... + Moving this here to simplify the view. + Should really handle the case where these are missing... """ td_list = [] # parse - td_list.append( _subtract_or_na(mdict, 'config_parse', 'config_download')) + td_list.append(_subtract_or_na(mdict, 'config_parse', 'config_download')) #probe - td_list.append( _subtract_or_na(mdict, 'probe_upload', 'start')) + td_list.append(_subtract_or_na(mdict, 'probe_upload', 'start')) #inventory - td_list.append( _subtract_or_na(mdict, 'inventory', 'initialization')) + td_list.append(_subtract_or_na(mdict, 'inventory', 'initialization')) #install - td_list.append( _subtract_or_na(mdict, 'install', 'inventory')) + td_list.append(_subtract_or_na(mdict, 'install', 'inventory')) #cfg download & parse - td_list.append( _subtract_or_na(mdict, 'config_parse', 'probe_upload')) + td_list.append(_subtract_or_na(mdict, 'config_parse', 'probe_upload')) #total - td_list.append( _subtract_or_na(mdict, 'finished', 'start')) + td_list.append(_subtract_or_na(mdict, 'finished', 'start')) return td_list + @register.filter def isstale(timestamp, entry_max=None): """ Check for a stale timestamp - Compares two timestamps and returns True if the + Compares two timestamps and returns True if the difference is greater then 24 hours. """ if not entry_max: entry_max = datetime.now() return entry_max - timestamp > timedelta(hours=24) + @register.filter def sort_interactions_by_name(value): """ Sort an interaction list by client name """ inters = list(value) - inters.sort(lambda a,b: cmp(a.client.name, b.client.name)) + inters.sort(lambda a, b: cmp(a.client.name, b.client.name)) return inters + class AddUrlFilter(template.Node): def __init__(self, filter_name, filter_value): self.filter_name = filter_name @@ -196,7 +209,7 @@ class AddUrlFilter(template.Node): link = reverse(view, args=args, kwargs=kwargs) except NoReverseMatch: link = reverse(self.fallback_view, args=None, - kwargs={ filter_name: filter_value }) + kwargs={filter_name: filter_value}) except NoReverseMatch: rm = sys.exc_info()[1] raise rm @@ -204,6 +217,7 @@ class AddUrlFilter(template.Node): pass return link + @register.tag def add_url_filter(parser, token): """ @@ -227,6 +241,7 @@ def add_url_filter(parser, token): return AddUrlFilter(filter_name, filter_value) + @register.filter def sortwell(value): """ @@ -235,10 +250,11 @@ def sortwell(value): """ configItems = list(value) - configItems.sort(lambda x,y: cmp(x.entry.name, y.entry.name)) - configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) + configItems.sort(lambda x, y: cmp(x.entry.name, y.entry.name)) + 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 @@ -246,19 +262,20 @@ class MediaTag(template.Node): 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('/')) + 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): """ @@ -267,10 +284,30 @@ def to_media_url(parser, token): {% to_media_url /bcfg2.css %} """ try: - tag_name, filter_value = token.split_contents() + filter_value = token.split_contents()[1] 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) +@register.filter +def determine_client_state(entry): + """ + Determine client state. + + This is used to determine whether a client is reporting clean or + dirty. If the client is reporting dirty, this will figure out just + _how_ dirty and adjust the color accordingly. + """ + if entry.state == 'clean': + return "clean-lineitem" + + bad_percentage = 100 * (float(entry.badcount()) / entry.totalcount) + if bad_percentage < 33: + thisdirty = "slightly-dirty-lineitem" + elif bad_percentage < 66: + thisdirty = "dirty-lineitem" + else: + thisdirty = "very-dirty-lineitem" + return thisdirty diff --git a/src/lib/Bcfg2/Server/Reports/reports/templatetags/split.py b/src/lib/Bcfg2/Server/Reports/reports/templatetags/split.py new file mode 100644 index 000000000..a9b4f0371 --- /dev/null +++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/split.py @@ -0,0 +1,8 @@ +from django import template +register = template.Library() + + +@register.filter +def split(s): + """split by newlines""" + return s.split('\n') diff --git a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py b/src/lib/Bcfg2/Server/Reports/reports/templatetags/syntax_coloring.py index 2e30125f9..36d4cf693 100644 --- a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py +++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/syntax_coloring.py @@ -1,6 +1,6 @@ import sys from django import template -from django.utils.encoding import smart_unicode, smart_str +from django.utils.encoding import smart_unicode from django.utils.html import conditional_escape from django.utils.safestring import mark_safe @@ -15,6 +15,7 @@ try: except: colorize = False + # py3k compatibility def u_str(string): if sys.hexversion >= 0x03000000: @@ -22,10 +23,12 @@ def u_str(string): else: return unicode(string) + @register.filter def syntaxhilight(value, arg="diff", autoescape=None): """ - Returns a syntax-hilighted version of Code; requires code/language arguments + Returns a syntax-hilighted version of Code; + requires code/language arguments """ if autoescape: @@ -44,6 +47,6 @@ def syntaxhilight(value, arg="diff", autoescape=None): except: return value else: - return mark_safe(u_str('<div class="note-box">Tip: Install pygments for highlighting</div><pre>%s</pre>') % value) + return mark_safe(u_str('<div class="note-box">Tip: Install pygments ' + 'for highlighting</div><pre>%s</pre>') % value) syntaxhilight.needs_autoescape = True - diff --git a/src/lib/Server/Reports/reports/urls.py b/src/lib/Bcfg2/Server/Reports/reports/urls.py index 434ce07b7..434ce07b7 100644 --- a/src/lib/Server/Reports/reports/urls.py +++ b/src/lib/Bcfg2/Server/Reports/reports/urls.py diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Bcfg2/Server/Reports/reports/views.py index ccd71a60e..ccd71a60e 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Bcfg2/Server/Reports/reports/views.py diff --git a/src/lib/Server/Reports/settings.py b/src/lib/Bcfg2/Server/Reports/settings.py index 952e3eae6..4d567f1a2 100644 --- a/src/lib/Server/Reports/settings.py +++ b/src/lib/Bcfg2/Server/Reports/settings.py @@ -64,10 +64,10 @@ if django.VERSION[0] == 1 and django.VERSION[1] < 2: # Local time zone for this installation. All choices can be found here: # http://docs.djangoproject.com/en/dev/ref/settings/#time-zone -try: - TIME_ZONE = c.get('statistics', 'time_zone') -except: - if django.VERSION[0] == 1 and django.VERSION[1] > 2: +if django.VERSION[0] == 1 and django.VERSION[1] > 2: + try: + TIME_ZONE = c.get('statistics', 'time_zone') + except: TIME_ZONE = None # Language code for this installation. All choices can be found here: @@ -124,8 +124,8 @@ except ImportError: django.contrib.auth.LOGIN_URL = '/login' SESSION_EXPIRE_AT_BROWSER_CLOSE = True - - + + TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates". diff --git a/src/lib/Server/Reports/updatefix.py b/src/lib/Bcfg2/Server/Reports/updatefix.py index 7cebaaca9..192b94b61 100644 --- a/src/lib/Server/Reports/updatefix.py +++ b/src/lib/Bcfg2/Server/Reports/updatefix.py @@ -1,8 +1,9 @@ import Bcfg2.Server.Reports.settings -from django.db import connection +from django.db import connection, DatabaseError import django.core.management import logging +import sys import traceback from Bcfg2.Server.Reports.reports.models import InternalDatabaseVersion, \ TYPE_BAD, TYPE_MODIFIED, TYPE_EXTRA @@ -16,9 +17,9 @@ def _merge_database_table_entries(): find_cursor = connection.cursor() cursor.execute(""" Select name, kind from reports_bad - union + union select name, kind from reports_modified - union + union select name, kind from reports_extra """) # this fetch could be better done @@ -51,7 +52,7 @@ def _merge_database_table_entries(): def _interactions_constraint_or_idx(): - '''sqlite doesn't support alter tables.. or constraints''' + """sqlite doesn't support alter tables.. or constraints""" cursor = connection.cursor() try: cursor.execute('alter table reports_interaction add constraint reports_interaction_20100601 unique (client_id,timestamp)') @@ -59,6 +60,88 @@ def _interactions_constraint_or_idx(): cursor.execute('create unique index reports_interaction_20100601 on reports_interaction (client_id,timestamp)') +def _remove_table_column(tbl, col): + """sqlite doesn't support deleting a column via alter table""" + cursor = connection.cursor() + db_engine = Bcfg2.Server.Reports.settings.DATABASES['default']['ENGINE'] + if db_engine == 'django.db.backends.mysql': + db_name = Bcfg2.Server.Reports.settings.DATABASES['default']['NAME'] + column_exists = cursor.execute('select * from information_schema.columns ' + 'where table_schema="%s" and ' + 'table_name="%s" ' + 'and column_name="%s";' % (db_name, tbl, col)) + if not column_exists: + # column doesn't exist + return + # if column exists from previous database, remove it + cursor.execute('alter table %s ' + 'drop column %s;' % (tbl, col)) + elif db_engine == 'django.db.backends.sqlite3': + # check if table exists + try: + cursor.execute('select * from sqlite_master where name=%s and type="table";' % tbl) + except DatabaseError: + # table doesn't exist + return + + # sqlite wants us to create a new table containing the columns we want + # and copy into it http://www.sqlite.org/faq.html#q11 + tmptbl_name = "t_backup" + _tmptbl_create = \ +"""create temporary table "%s" ( + "id" integer NOT NULL PRIMARY KEY, + "client_id" integer NOT NULL REFERENCES "reports_client" ("id"), + "timestamp" datetime NOT NULL, + "state" varchar(32) NOT NULL, + "repo_rev_code" varchar(64) NOT NULL, + "goodcount" integer NOT NULL, + "totalcount" integer NOT NULL, + "server" varchar(256) NOT NULL, + "bad_entries" integer NOT NULL, + "modified_entries" integer NOT NULL, + "extra_entries" integer NOT NULL, + UNIQUE ("client_id", "timestamp") +);""" % tmptbl_name + _newtbl_create = \ +"""create table "%s" ( + "id" integer NOT NULL PRIMARY KEY, + "client_id" integer NOT NULL REFERENCES "reports_client" ("id"), + "timestamp" datetime NOT NULL, + "state" varchar(32) NOT NULL, + "repo_rev_code" varchar(64) NOT NULL, + "goodcount" integer NOT NULL, + "totalcount" integer NOT NULL, + "server" varchar(256) NOT NULL, + "bad_entries" integer NOT NULL, + "modified_entries" integer NOT NULL, + "extra_entries" integer NOT NULL, + UNIQUE ("client_id", "timestamp") +);""" % tbl + new_cols = "id,\ + client_id,\ + timestamp,\ + state,\ + repo_rev_code,\ + goodcount,\ + totalcount,\ + server,\ + bad_entries,\ + modified_entries,\ + extra_entries" + + delete_col = [_tmptbl_create, + "insert into %s select %s from %s;" % (tmptbl_name, new_cols, tbl), + "drop table %s" % tbl, + _newtbl_create, + "create index reports_interaction_client_id on %s (client_id);" % tbl, + "insert into %s select %s from %s;" % (tbl, new_cols, + tmptbl_name), + "drop table %s;" % tmptbl_name] + + for sql in delete_col: + cursor.execute(sql) + + def _populate_interaction_entry_counts(): '''Populate up the type totals for the interaction table''' cursor = connection.cursor() @@ -103,6 +186,8 @@ _fixes = [_merge_database_table_entries, _interactions_constraint_or_idx, 'alter table reports_reason add is_binary bool NOT NULL default False;', 'alter table reports_reason add is_sensitive bool NOT NULL default False;', + _remove_table_column('reports_interaction', 'client_version'), + "alter table reports_reason add unpruned varchar(1280) not null default '';", ] # this will calculate the last possible version of the database @@ -110,7 +195,7 @@ lastversion = len(_fixes) def rollupdate(current_version): - """ function responsible to coordinates all the updates + """function responsible to coordinates all the updates need current_version as integer """ ret = None @@ -122,8 +207,10 @@ def rollupdate(current_version): else: _fixes[i]() except: - logger.error("Failed to perform db update %s" % (_fixes[i]), exc_info=1) - # since array start at 0 but version start at 1 we add 1 to the normal count + logger.error("Failed to perform db update %s" % (_fixes[i]), + exc_info=1) + # since the array starts at 0 but version + # starts at 1 we add 1 to the normal count ret = InternalDatabaseVersion.objects.create(version=i + 1) return ret else: @@ -135,16 +222,19 @@ def dosync(): # try to detect if it's a fresh new database try: cursor = connection.cursor() - # If this table goes missing then don't forget to change it to the new one + # If this table goes missing, + # don't forget to change it to the new one cursor.execute("Select * from reports_client") # if we get here with no error then the database has existing tables fresh = False except: - logger.debug("there was an error while detecting the freshness of the database") + logger.debug("there was an error while detecting " + "the freshness of the database") #we should get here if the database is new fresh = True - # ensure database connection are close, so that the management can do it's job right + # ensure database connections are closed + # so that the management can do its job right try: cursor.close() connection.close() @@ -169,7 +259,8 @@ def dosync(): def update_database(): - ''' methode to search where we are in the revision of the database models and update them ''' + """method to search where we are in the revision + of the database models and update them""" try: logger.debug("Running upgrade of models to the new one") dosync() diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Bcfg2/Server/Reports/urls.py index d7ff1eee5..d7ff1eee5 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Bcfg2/Server/Reports/urls.py diff --git a/src/lib/Server/Reports/utils.py b/src/lib/Bcfg2/Server/Reports/utils.py index e0b6ead59..e0b6ead59 100755 --- a/src/lib/Server/Reports/utils.py +++ b/src/lib/Bcfg2/Server/Reports/utils.py diff --git a/src/lib/Server/Snapshots/__init__.py b/src/lib/Bcfg2/Server/Snapshots/__init__.py index 7c901adb2..7c901adb2 100644 --- a/src/lib/Server/Snapshots/__init__.py +++ b/src/lib/Bcfg2/Server/Snapshots/__init__.py diff --git a/src/lib/Server/Snapshots/model.py b/src/lib/Bcfg2/Server/Snapshots/model.py index 5d7973c16..5d7973c16 100644 --- a/src/lib/Server/Snapshots/model.py +++ b/src/lib/Bcfg2/Server/Snapshots/model.py diff --git a/src/lib/Server/__init__.py b/src/lib/Bcfg2/Server/__init__.py index bca73ded7..320371284 100644 --- a/src/lib/Server/__init__.py +++ b/src/lib/Bcfg2/Server/__init__.py @@ -1,9 +1,7 @@ -# $Id$ """This is the set of modules for Bcfg2.Server.""" import lxml.etree -__revision__ = '$Revision$' __all__ = ["Admin", "Core", "FileMonitor", "Plugin", "Plugins", "Hostbase", "Reports", "Snapshots", "XMLParser"] diff --git a/src/lib/Statistics.py b/src/lib/Bcfg2/Statistics.py index a0cb8f39b..a0cb8f39b 100644 --- a/src/lib/Statistics.py +++ b/src/lib/Bcfg2/Statistics.py diff --git a/src/lib/__init__.py b/src/lib/Bcfg2/__init__.py index d36c0a00a..357f66f6d 100644 --- a/src/lib/__init__.py +++ b/src/lib/Bcfg2/__init__.py @@ -1,4 +1,3 @@ """Base modules definition.""" -__revision__ = '$Revision$' __all__ = ['Server', 'Client', 'Component', 'Logger', 'Options', 'Proxy', 'Statistics'] diff --git a/src/lib/Bcfg2Py3Incompat.py b/src/lib/Bcfg2Py3Incompat.py deleted file mode 100644 index 6b66e72b0..000000000 --- a/src/lib/Bcfg2Py3Incompat.py +++ /dev/null @@ -1,2 +0,0 @@ -def fprint(s, f): - print(s, file=f) diff --git a/src/lib/Client/Tools/Portage.py b/src/lib/Client/Tools/Portage.py deleted file mode 100644 index 17163afa9..000000000 --- a/src/lib/Client/Tools/Portage.py +++ /dev/null @@ -1,72 +0,0 @@ -"""This is the Bcfg2 tool for the Gentoo Portage system.""" -__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.""" - name = 'Portage' - __execs__ = ['/usr/bin/emerge', '/usr/bin/equery'] - __handles__ = [('Package', 'ebuild')] - __req__ = {'Package': ['name', 'version']} - pkgtype = 'ebuild' - # requires a working PORTAGE_BINHOST in make.conf - pkgtool = ('emerge --getbinpkgonly %s', ('=%s-%s', ['name', 'version'])) - - def __init__(self, logger, cfg, setup): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, cfg, setup) - self.__important__ = self.__important__ + ['/etc/make.conf'] - self.cfg = cfg - self.installed = {} - self.RefreshPackages() - - def RefreshPackages(self): - """Refresh memory hashes of packages.""" - ret, cache = self.cmd.run("equery -q list '*'") - if ret == 2: - cache = self.cmd.run("equery -q list '*'")[1] - pattern = re.compile('(.*)-(\d.*)') - self.installed = {} - for pkg in cache: - if pattern.match(pkg): - name = pattern.match(pkg).group(1) - version = pattern.match(pkg).group(2) - self.installed[name] = version - else: - self.logger.info("Failed to parse pkg name %s" % pkg) - - def VerifyPackage(self, entry, modlist): - """Verify package for entry.""" - if not 'version' in entry.attrib: - self.logger.info("Cannot verify unversioned package %s" % - (entry.attrib['name'])) - return False - if entry.attrib['name'] in self.installed: - 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 -N 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]: - return False - return True - else: - entry.set('current_version', self.installed[entry.get('name')]) - return False - entry.set('current_exists', 'false') - return False - - def RemovePackages(self, packages): - """Deal with extra configuration detected.""" - pkgnames = " ".join([pkg.get('name') for pkg in packages]) - if len(packages) > 0: - self.logger.info('Removing packages:') - self.logger.info(pkgnames) - self.cmd.run("emerge --unmerge --quiet %s" % " ".join(pkgnames.split(' '))) - self.RefreshPackages() - self.extra = self.FindExtraPackages() diff --git a/src/lib/Server/Lint/Deltas.py b/src/lib/Server/Lint/Deltas.py index cf91d1d13..114f2e348 100644 --- a/src/lib/Server/Lint/Deltas.py +++ b/src/lib/Server/Lint/Deltas.py @@ -1,4 +1,5 @@ import Bcfg2.Server.Lint +from Bcfg2.Server.Plugins.Cfg import CfgFilter class Deltas(Bcfg2.Server.Lint.ServerPlugin): """ Warn about usage of .cat and .diff files """ @@ -10,11 +11,15 @@ class Deltas(Bcfg2.Server.Lint.ServerPlugin): for basename, entry in list(cfg.entries.items()): self.check_entry(basename, entry) + @classmethod + def Errors(cls): + return {"cat-file-used":"warning", + "diff-file-used":"warning"} + def check_entry(self, basename, entry): - for fname in list(entry.entries.keys()): - if self.HandlesFile(fname): - match = entry.specific.delta_reg.match(fname) - if match: - self.LintError("%s-file-used" % match.group('delta'), - "%s file used on %s: %s" % - (match.group('delta'), basename, fname)) + for fname, processor in entry.entries.items(): + if self.HandlesFile(fname) and isinstance(processor, CfgFilter): + extension = fname.split(".")[-1] + self.LintError("%s-file-used" % extension, + "%s file used on %s: %s" % + (extension, basename, fname)) diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py deleted file mode 100644 index 9ec39e108..000000000 --- a/src/lib/Server/Plugins/Cfg.py +++ /dev/null @@ -1,295 +0,0 @@ -"""This module implements a config file repository.""" -__revision__ = '$Revision$' - -import binascii -import logging -import lxml -import operator -import os -import os.path -import re -import stat -import sys -import tempfile -from subprocess import Popen, PIPE -from Bcfg2.Bcfg2Py3k import u_str - -import Bcfg2.Server.Plugin - -try: - import genshi.core - import genshi.input - from genshi.template import TemplateLoader, NewTextTemplate - have_genshi = True -except: - have_genshi = False - -try: - import Cheetah.Template - import Cheetah.Parser - have_cheetah = True -except: - have_cheetah = False - -# setup logging -logger = logging.getLogger('Bcfg2.Plugins.Cfg') - - -# snipped from TGenshi -def removecomment(stream): - """A genshi filter that removes comments from the stream.""" - for kind, data, pos in stream: - if kind is genshi.core.COMMENT: - continue - yield kind, data, pos - - -def process_delta(data, delta): - if not delta.specific.delta: - return data - if delta.specific.delta == 'cat': - datalines = data.strip().split('\n') - for line in delta.data.split('\n'): - if not line: - continue - if line[0] == '+': - datalines.append(line[1:]) - elif line[0] == '-': - if line[1:] in datalines: - datalines.remove(line[1:]) - return "\n".join(datalines) + "\n" - elif delta.specific.delta == 'diff': - basehandle, basename = tempfile.mkstemp() - basefile = open(basename, 'w') - basefile.write(data) - basefile.close() - os.close(basehandle) - - cmd = ["patch", "-u", "-f", basefile.name] - patch = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) - stderr = patch.communicate(input=delta.data)[1] - ret = patch.wait() - output = open(basefile.name, 'r').read() - os.unlink(basefile.name) - if ret >> 8 != 0: - logger.error("Error applying diff %s: %s" % (delta.name, stderr)) - raise Bcfg2.Server.Plugin.PluginExecutionError('delta', delta) - return output - - -class CfgMatcher: - - def __init__(self, fname): - name = re.escape(fname) - self.basefile_reg = re.compile('^(?P<basename>%s)(|\\.H_(?P<hostname>\S+?)|.G(?P<prio>\d+)_(?P<group>\S+?))((?P<genshi>\\.genshi)|(?P<cheetah>\\.cheetah))?$' % name) - self.delta_reg = re.compile('^(?P<basename>%s)(|\\.H_(?P<hostname>\S+)|\\.G(?P<prio>\d+)_(?P<group>\S+))\\.(?P<delta>(cat|diff))$' % name) - self.cat_count = fname.count(".cat") - self.diff_count = fname.count(".diff") - - def match(self, fname): - if fname.count(".cat") > self.cat_count \ - or fname.count('.diff') > self.diff_count: - return self.delta_reg.match(fname) - return self.basefile_reg.match(fname) - - -class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): - - def __init__(self, basename, path, entry_type, encoding): - Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, - entry_type, encoding) - self.specific = CfgMatcher(path.split('/')[-1]) - path = path - - def debug_log(self, message, flag=None): - if (flag is None and self.debug_flag) or flag: - logger.error(message) - - def sort_by_specific(self, one, other): - return cmp(one.specific, other.specific) - - def get_pertinent_entries(self, entry, metadata): - """return a list of all entries pertinent - to a client => [base, delta1, delta2] - """ - matching = [ent for ent in list(self.entries.values()) if \ - ent.specific.matches(metadata)] - matching.sort(key=operator.attrgetter('specific')) - # base entries which apply to a client - # (e.g. foo, foo.G##_groupname, foo.H_hostname) - base_files = [matching.index(m) for m in matching - if not m.specific.delta] - if not base_files: - msg = "No base file found for %s" % entry.get('name') - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - base = min(base_files) - used = matching[:base + 1] - used.reverse() - return used - - def bind_entry(self, entry, metadata): - self.bind_info_to_entry(entry, metadata) - used = self.get_pertinent_entries(entry, metadata) - basefile = used.pop(0) - if entry.get('perms').lower() == 'inherit': - # use on-disk permissions - fname = os.path.join(self.path, entry.get('name')) - entry.set('perms', - str(oct(stat.S_IMODE(os.stat(fname).st_mode)))) - if entry.tag == 'Path': - entry.set('type', 'file') - if basefile.name.endswith(".genshi"): - if not have_genshi: - msg = "Cfg: Genshi is not available: %s" % entry.get("name") - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - try: - template_cls = NewTextTemplate - loader = TemplateLoader() - template = loader.load(basefile.name, cls=template_cls, - encoding=self.encoding) - fname = entry.get('realname', entry.get('name')) - stream = template.generate(name=fname, - metadata=metadata, - path=basefile.name).filter(removecomment) - try: - data = stream.render('text', encoding=self.encoding, - strip_whitespace=False) - except TypeError: - data = stream.render('text', encoding=self.encoding) - if data == '': - entry.set('empty', 'true') - except Exception: - msg = "Cfg: genshi exception (%s): %s" % (entry.get("name"), - sys.exc_info()[1]) - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - elif basefile.name.endswith(".cheetah"): - if not have_cheetah: - msg = "Cfg: Cheetah is not available: %s" % entry.get("name") - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - try: - fname = entry.get('realname', entry.get('name')) - s = {'useStackFrames': False} - template = Cheetah.Template.Template(open(basefile.name).read(), - compilerSettings=s) - template.metadata = metadata - template.path = fname - template.source_path = basefile.name - data = template.respond() - if data == '': - entry.set('empty', 'true') - except Exception: - msg = "Cfg: cheetah exception (%s): %s" % (entry.get("name"), - sys.exc_info()[1]) - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - else: - data = basefile.data - for delta in used: - data = process_delta(data, delta) - if entry.get('encoding') == 'base64': - entry.text = binascii.b2a_base64(data) - else: - try: - entry.text = u_str(data, self.encoding) - except UnicodeDecodeError: - msg = "Failed to decode %s: %s" % (entry.get('name'), - sys.exc_info()[1]) - logger.error(msg) - logger.error("Please verify you are using the proper encoding.") - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - except ValueError: - msg = "Error in specification for %s: %s" % (entry.get('name'), - sys.exc_info()[1]) - logger.error(msg) - logger.error("You need to specify base64 encoding for %s." % - entry.get('name')) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - if entry.text in ['', None]: - entry.set('empty', 'true') - - def list_accept_choices(self, entry, metadata): - '''return a list of candidate pull locations''' - used = self.get_pertinent_entries(entry, metadata) - ret = [] - if used: - ret.append(used[0].specific) - if not ret[0].hostname: - ret.append(Bcfg2.Server.Plugin.Specificity(hostname=metadata.hostname)) - return ret - - def build_filename(self, specific): - bfname = self.path + '/' + self.path.split('/')[-1] - if specific.all: - return bfname - elif specific.group: - return "%s.G%02d_%s" % (bfname, specific.prio, specific.group) - elif specific.hostname: - return "%s.H_%s" % (bfname, specific.hostname) - - def write_update(self, specific, new_entry, log): - if 'text' in new_entry: - name = self.build_filename(specific) - if os.path.exists("%s.genshi" % name): - msg = "Cfg: Unable to pull data for genshi types" - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - elif os.path.exists("%s.cheetah" % name): - msg = "Cfg: Unable to pull data for cheetah types" - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - try: - etext = new_entry['text'].encode(self.encoding) - except: - msg = "Cfg: Cannot encode content of %s as %s" % (name, - self.encoding) - logger.error(msg) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - open(name, 'w').write(etext) - self.debug_log("Wrote file %s" % name, flag=log) - badattr = [attr for attr in ['owner', 'group', 'perms'] - if attr in new_entry] - if badattr: - # check for info files and inform user of their removal - if os.path.exists(self.path + "/:info"): - logger.info("Removing :info file and replacing with " - "info.xml") - os.remove(self.path + "/:info") - if os.path.exists(self.path + "/info"): - logger.info("Removing info file and replacing with " - "info.xml") - os.remove(self.path + "/info") - metadata_updates = {} - metadata_updates.update(self.metadata) - for attr in badattr: - metadata_updates[attr] = new_entry.get(attr) - infoxml = lxml.etree.Element('FileInfo') - infotag = lxml.etree.SubElement(infoxml, 'Info') - [infotag.attrib.__setitem__(attr, metadata_updates[attr]) \ - for attr in metadata_updates] - ofile = open(self.path + "/info.xml", "w") - ofile.write(lxml.etree.tostring(infoxml, pretty_print=True)) - ofile.close() - self.debug_log("Wrote file %s" % (self.path + "/info.xml"), - flag=log) - - -class Cfg(Bcfg2.Server.Plugin.GroupSpool, - Bcfg2.Server.Plugin.PullTarget): - """This generator in the configuration file repository for Bcfg2.""" - name = 'Cfg' - __version__ = '$Id$' - __author__ = 'bcfg-dev@mcs.anl.gov' - es_cls = CfgEntrySet - es_child_cls = Bcfg2.Server.Plugin.SpecificData - - def AcceptChoices(self, entry, metadata): - return self.entries[entry.get('name')].list_accept_choices(entry, metadata) - - def AcceptPullData(self, specific, new_entry, log): - return self.entries[new_entry.get('name')].write_update(specific, - new_entry, - log) diff --git a/src/lib/Server/Plugins/Packages/PackagesConfig.py b/src/lib/Server/Plugins/Packages/PackagesConfig.py deleted file mode 100644 index 7950f15e6..000000000 --- a/src/lib/Server/Plugins/Packages/PackagesConfig.py +++ /dev/null @@ -1,15 +0,0 @@ -import Bcfg2.Server.Plugin - -class PackagesConfig(Bcfg2.Server.Plugin.SimpleConfig): - _required = False - - def Index(self): - """ Build local data structures """ - Bcfg2.Server.Plugin.SimpleConfig.Index(self) - - if hasattr(self.plugin, "sources") and self.plugin.sources.loaded: - # only reload Packages plugin if sources have been loaded. - # otherwise, this is getting called on server startup, and - # we have to wait until all sources have been indexed - # before we can call Packages.Reload() - self.plugin.Reload() diff --git a/src/lib/Server/Reports/reports/templates/clients/index.html b/src/lib/Server/Reports/reports/templates/clients/index.html deleted file mode 100644 index e0c0d2d7a..000000000 --- a/src/lib/Server/Reports/reports/templates/clients/index.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "base-timeview.html" %} - -{% block extra_header_info %} -{% endblock%} - -{% block title %}Bcfg2 - Client Grid View{% endblock %} - -{% block pagebanner %}Clients - Grid View{% endblock %} - -{% block content %} - -{% if inter_list %} - <table class='grid-view' align='center'> - {% for inter in inter_list %} - {% if forloop.first %}<tr>{% endif %} - <td class="{{inter.state}}-lineitem"> - <a href="{% spaceless %}{% if not timestamp %} - {% url reports_client_detail inter.client.name %} - {% else %} - {% url reports_client_detail_pk inter.client.name,inter.id %} - {% endif %} - {% endspaceless %}">{{ inter.client.name }}</a> - </td> - {% if forloop.last %} - </tr> - {% else %} - {% if forloop.counter|divisibleby:"4" %}</tr><tr>{% endif %} - {% endif %} - {% endfor %} - </table> -{% else %} - <p>No client records are available.</p> -{% endif %} -{% endblock %} |