summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README20
-rw-r--r--debian/bcfg2-doc.links1
-rw-r--r--debian/bcfg2-server.README.Debian (renamed from debian/bcfg2-server.README.debian)0
-rw-r--r--debian/bcfg2-server.logcheck.ignore.server2
-rw-r--r--debian/bcfg2-server.preinst13
-rw-r--r--debian/bcfg2.preinst13
-rw-r--r--debian/changelog12
-rw-r--r--debian/control15
-rwxr-xr-xdebian/rules11
-rw-r--r--doc/_templates/indexsidebar.html11
-rw-r--r--doc/appendix/guides/centos.txt156
-rw-r--r--doc/appendix/guides/fedora.txt2
-rw-r--r--doc/appendix/guides/gentoo.txt73
-rw-r--r--doc/appendix/guides/ubuntu.txt4
-rw-r--r--doc/appendix/guides/web-reports-install.txt2
-rw-r--r--doc/client/tools/yumng.txt3
-rw-r--r--doc/conf.py10
-rw-r--r--doc/development/index.txt1
-rw-r--r--doc/development/plugins.txt4
-rw-r--r--doc/development/setup.txt7
-rw-r--r--doc/development/unit-testing.txt25
-rw-r--r--doc/glossary.txt2
-rw-r--r--doc/installation/source.txt3
-rw-r--r--doc/reports/dynamic.txt9
-rw-r--r--doc/server/plugins/connectors/templatehelper.txt74
-rw-r--r--doc/server/plugins/generators/packages.txt293
-rw-r--r--doc/server/plugins/generators/rules.txt18
-rw-r--r--doc/server/plugins/generators/tgenshi/index.txt2
-rw-r--r--doc/server/plugins/generators/tgenshi/iptables.txt21
-rw-r--r--doc/server/plugins/grouping/metadata.txt29
-rw-r--r--doc/server/plugins/probes/group.txt6
-rw-r--r--doc/server/plugins/probes/index.txt2
-rw-r--r--examples/Bundler/sgenshi-dirvish.genshi2
-rw-r--r--examples/TemplateHelper/include.py97
-rw-r--r--examples/bcfg2-lint.conf2
-rw-r--r--gentoo/bcfg2-1.1.2.ebuild65
-rw-r--r--gentoo/bcfg2-1.2.2.ebuild79
-rw-r--r--man/bcfg2.conf.57
-rw-r--r--misc/bcfg2.spec238
-rw-r--r--osx/Makefile4
-rw-r--r--osx/macports/Portfile6
-rw-r--r--redhat/Makefile2
-rw-r--r--redhat/VERSION2
-rw-r--r--redhat/bcfg2.spec.in6
-rw-r--r--reports/xsl-transforms/xsl-transform-includes/html-templates.xsl2
-rw-r--r--schemas/base.xsd1
-rw-r--r--schemas/bundle.xsd19
-rw-r--r--schemas/clients.xsd9
-rw-r--r--schemas/decisions.xsd1
-rw-r--r--schemas/defaults.xsd1
-rw-r--r--schemas/genshi.xsd23
-rw-r--r--schemas/grouppatterns.xsd6
-rw-r--r--schemas/metadata.xsd1
-rw-r--r--schemas/packages.xsd40
-rw-r--r--schemas/pathentry.xsd7
-rw-r--r--schemas/pkglist.xsd1
-rw-r--r--schemas/pkgtype.xsd12
-rw-r--r--schemas/rules.xsd17
-rw-r--r--schemas/services.xsd1
-rw-r--r--schemas/servicetype.xsd9
-rw-r--r--schemas/types.xsd1
-rwxr-xr-xsetup.py92
-rw-r--r--solaris/Makefile22
-rw-r--r--solaris/gen-prototypes.sh24
-rw-r--r--solaris/pkginfo.bcfg22
-rw-r--r--solaris/pkginfo.bcfg2-server2
-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)44
-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)14
-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.py125
-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)86
-rw-r--r--src/lib/Bcfg2/Client/Tools/__init__.py (renamed from src/lib/Client/Tools/__init__.py)31
-rw-r--r--src/lib/Bcfg2/Client/Tools/launchd.py (renamed from src/lib/Client/Tools/launchd.py)54
-rwxr-xr-xsrc/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)3
-rw-r--r--src/lib/Bcfg2/Options.py (renamed from src/lib/Options.py)27
-rw-r--r--src/lib/Bcfg2/Proxy.py (renamed from src/lib/Proxy.py)36
-rw-r--r--src/lib/Bcfg2/SSLServer.py (renamed from src/lib/SSLServer.py)2
-rw-r--r--src/lib/Bcfg2/Server/Admin/Backup.py (renamed from src/lib/Server/Admin/Backup.py)10
-rw-r--r--src/lib/Bcfg2/Server/Admin/Bundle.py (renamed from src/lib/Server/Admin/Bundle.py)16
-rw-r--r--src/lib/Bcfg2/Server/Admin/Client.py (renamed from src/lib/Server/Admin/Client.py)4
-rw-r--r--src/lib/Bcfg2/Server/Admin/Compare.py (renamed from src/lib/Server/Admin/Compare.py)4
-rw-r--r--src/lib/Bcfg2/Server/Admin/Group.py (renamed from src/lib/Server/Admin/Group.py)4
-rw-r--r--src/lib/Bcfg2/Server/Admin/Init.py (renamed from src/lib/Server/Admin/Init.py)9
-rw-r--r--src/lib/Bcfg2/Server/Admin/Minestruct.py (renamed from src/lib/Server/Admin/Minestruct.py)4
-rw-r--r--src/lib/Bcfg2/Server/Admin/Perf.py (renamed from src/lib/Server/Admin/Perf.py)5
-rw-r--r--src/lib/Bcfg2/Server/Admin/Pull.py (renamed from src/lib/Server/Admin/Pull.py)7
-rw-r--r--src/lib/Bcfg2/Server/Admin/Query.py (renamed from src/lib/Server/Admin/Query.py)24
-rw-r--r--src/lib/Bcfg2/Server/Admin/Reports.py (renamed from src/lib/Server/Admin/Reports.py)32
-rw-r--r--src/lib/Bcfg2/Server/Admin/Snapshots.py (renamed from src/lib/Server/Admin/Snapshots.py)9
-rw-r--r--src/lib/Bcfg2/Server/Admin/Tidy.py (renamed from src/lib/Server/Admin/Tidy.py)11
-rw-r--r--src/lib/Bcfg2/Server/Admin/Viz.py (renamed from src/lib/Server/Admin/Viz.py)16
-rw-r--r--src/lib/Bcfg2/Server/Admin/Xcmd.py (renamed from src/lib/Server/Admin/Xcmd.py)5
-rw-r--r--src/lib/Bcfg2/Server/Admin/__init__.py (renamed from src/lib/Server/Admin/__init__.py)60
-rw-r--r--src/lib/Bcfg2/Server/Core.py (renamed from src/lib/Server/Core.py)25
-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-xsrc/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)20
-rw-r--r--src/lib/Bcfg2/Server/Lint/Comments.py (renamed from src/lib/Server/Lint/Comments.py)14
-rw-r--r--src/lib/Bcfg2/Server/Lint/Deltas.py23
-rw-r--r--src/lib/Bcfg2/Server/Lint/Duplicates.py (renamed from src/lib/Server/Lint/Duplicates.py)9
-rwxr-xr-xsrc/lib/Bcfg2/Server/Lint/Genshi.py (renamed from src/lib/Server/Lint/Genshi.py)14
-rw-r--r--src/lib/Bcfg2/Server/Lint/GroupPatterns.py34
-rw-r--r--src/lib/Bcfg2/Server/Lint/InfoXML.py (renamed from src/lib/Server/Lint/InfoXML.py)7
-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)24
-rw-r--r--src/lib/Bcfg2/Server/Lint/RequiredAttrs.py (renamed from src/lib/Server/Lint/RequiredAttrs.py)8
-rw-r--r--src/lib/Bcfg2/Server/Lint/TemplateHelper.py63
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py (renamed from src/lib/Server/Lint/Validate.py)15
-rw-r--r--src/lib/Bcfg2/Server/Lint/__init__.py (renamed from src/lib/Server/Lint/__init__.py)66
-rw-r--r--src/lib/Bcfg2/Server/Plugin.py (renamed from src/lib/Server/Plugin.py)242
-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)4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Bundler.py (renamed from src/lib/Server/Plugins/Bundler.py)42
-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.py (renamed from src/lib/Server/Plugins/Cfg.py)87
-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)6
-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)156
-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)25
-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)639
-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)13
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Collection.py (renamed from src/lib/Server/Plugins/Packages/Collection.py)56
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Pac.py (renamed from src/lib/Server/Plugins/Packages/Pac.py)19
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/PackagesConfig.py15
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py (renamed from src/lib/Server/Plugins/Packages/PackagesSources.py)46
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py (renamed from src/lib/Server/Plugins/Packages/Source.py)74
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py (renamed from src/lib/Server/Plugins/Packages/Yum.py)185
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py (renamed from src/lib/Server/Plugins/Packages/__init__.py)88
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Pkgmgr.py (renamed from src/lib/Server/Plugins/Pkgmgr.py)10
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Probes.py (renamed from src/lib/Server/Plugins/Probes.py)15
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py (renamed from src/lib/Server/Plugins/Properties.py)5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Rules.py (renamed from src/lib/Server/Plugins/Rules.py)28
-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)18
-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)4
-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)2
-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)12
-rw-r--r--src/lib/Bcfg2/Server/Plugins/TemplateHelper.py83
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Trigger.py (renamed from src/lib/Server/Plugins/Trigger.py)14
-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-xsrc/lib/Bcfg2/Server/Reports/importscript.py (renamed from src/lib/Server/Reports/importscript.py)11
-rwxr-xr-xsrc/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)2
-rw-r--r--src/lib/Bcfg2/Server/Reports/reports/sql/client.sql (renamed from src/lib/Server/Reports/reports/sql/client.sql)0
-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)2
-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)0
-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 (renamed from src/lib/Server/Reports/reports/templates/clients/index.html)0
-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)0
-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)4
-rw-r--r--src/lib/Bcfg2/Server/Reports/reports/templatetags/split.py8
-rw-r--r--src/lib/Bcfg2/Server/Reports/reports/templatetags/syntax_coloring.py (renamed from src/lib/Server/Reports/reports/templatetags/syntax_coloring.py)0
-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)35
-rw-r--r--src/lib/Bcfg2/Server/Reports/updatefix.py (renamed from src/lib/Server/Reports/updatefix.py)97
-rw-r--r--src/lib/Bcfg2/Server/Reports/urls.py (renamed from src/lib/Server/Reports/urls.py)0
-rwxr-xr-xsrc/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)2
-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.py2
-rw-r--r--src/lib/Client/Tools/Portage.py72
-rw-r--r--src/lib/Server/Plugins/Packages/PackagesConfig.py33
-rwxr-xr-xsrc/sbin/bcfg23
-rwxr-xr-xsrc/sbin/bcfg2-admin18
-rwxr-xr-xsrc/sbin/bcfg2-build-reports2
-rwxr-xr-xsrc/sbin/bcfg2-info276
-rwxr-xr-xsrc/sbin/bcfg2-lint1
-rwxr-xr-xsrc/sbin/bcfg2-ping-sweep2
-rwxr-xr-xsrc/sbin/bcfg2-reports1
-rwxr-xr-xsrc/sbin/bcfg2-server1
-rwxr-xr-x[-rw-r--r--]src/sbin/bcfg2-test107
-rwxr-xr-xsrc/sbin/bcfg2-yum-helper118
-rw-r--r--testsuite/TestFrame.py104
-rw-r--r--testsuite/TestOptions.py103
-rw-r--r--testsuite/TestPlugin.py122
-rw-r--r--testsuite/Testlib/TestOptions.py124
-rw-r--r--testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py905
-rwxr-xr-xtools/bcfg2-export-config1
-rwxr-xr-xtools/create-debian-pkglist.py1
-rwxr-xr-xtools/encap-util-count.sh1
-rwxr-xr-xtools/encap-util-expand.sh1
-rwxr-xr-xtools/encap-util-place.sh1
-rwxr-xr-xtools/encap-util-xml.sh1
-rwxr-xr-xtools/hostbasepush.py2
-rwxr-xr-xtools/pkgmgr_gen.py1
304 files changed, 4344 insertions, 2551 deletions
diff --git a/README b/README
index 7368776a4..7bc3388ce 100644
--- a/README
+++ b/README
@@ -30,18 +30,20 @@ following pages in the Bcfg2 wiki.
Need help
---------
-* FAQ: http://bcfg2.org/wiki/FAQ
-* IRC: #bcfg2 on chat.freenode.net
-* Mailing list: https://lists.mcs.anl.gov/mailman/listinfo/bcfg-dev
-* Bug tracker: http://bcfg2.org/report
+A lot of documentation is available in the Bcfg2 manual and the Bcfg2 wiki.
-Documentation
--------------
+* Documentation: http://docs.bcfg2.org/
+* Wiki: http://bcfg2.org/wiki/
+* FAQ: http://bcfg2.org/wiki/FAQ
+* IRC: #bcfg2 on chat.freenode.net
+* Mailing list: https://lists.mcs.anl.gov/mailman/listinfo/bcfg-dev
-A lot of documentation is available in the Bcfg2 manual and the Bcfg2 wiki.
+Want to help
+-------------
-Wiki: http://bcfg2.org/wiki/
-Manual: http://docs.bcfg2.org/
+* Bug tracker: http://bcfg2.org/report
+* Development: http://docs.bcfg2.org/development/
+* Wiki: http://bcfg2.org/wiki/Contribute
Bcfg2 is licensed under BSD, for more details check COPYING.
diff --git a/debian/bcfg2-doc.links b/debian/bcfg2-doc.links
new file mode 100644
index 000000000..133a58e2e
--- /dev/null
+++ b/debian/bcfg2-doc.links
@@ -0,0 +1 @@
+usr/share/doc/bcfg2/html/_sources usr/share/doc/bcfg2/rst
diff --git a/debian/bcfg2-server.README.debian b/debian/bcfg2-server.README.Debian
index 185dd2450..185dd2450 100644
--- a/debian/bcfg2-server.README.debian
+++ b/debian/bcfg2-server.README.Debian
diff --git a/debian/bcfg2-server.logcheck.ignore.server b/debian/bcfg2-server.logcheck.ignore.server
index a3e5a5a8d..136384f00 100644
--- a/debian/bcfg2-server.logcheck.ignore.server
+++ b/debian/bcfg2-server.logcheck.ignore.server
@@ -1,4 +1,4 @@
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ bcfg2-server\[[0-9]+\]: Processed [0-9]+ (fam|gamin) events in [0-9.]+ seconds\. [0-9]+ coalesced$
-^\w{3} [ :0-9]{11} [._[:alnum:]-]+ bcfg2-server\[[0-9]+\]: Generated config for [._[:alnum:]-]+ in [0-9.]+ seconds$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ bcfg2-server\[[0-9]+\]: Generated config for [._[:alnum:]-]+ in [0-9.]+ s$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ bcfg2-server\[[0-9]+\]: Client [._[:alnum:]-]+ reported state (clean|dirty)$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ bcfg2-server\[[0-9]+\]: Suppressing event for bogus file .*$
diff --git a/debian/bcfg2-server.preinst b/debian/bcfg2-server.preinst
deleted file mode 100644
index 2eb87e99b..000000000
--- a/debian/bcfg2-server.preinst
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-# This file can be removed after a few releases.
-set -e
-if [ "$1" = upgrade ]
-then
- if dpkg --compare-versions "$2" lt 1.0pre5-0.3; then
- if which pycentral > /dev/null; then
- pycentral pkgremove bcfg2-server
- fi
- fi
-fi
-
-#DEBHELPER#
diff --git a/debian/bcfg2.preinst b/debian/bcfg2.preinst
deleted file mode 100644
index be4b1444a..000000000
--- a/debian/bcfg2.preinst
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-# This file can be removed after a few releases.
-set -e
-if [ "$1" = upgrade ]
-then
- if dpkg --compare-versions "$2" lt 1.0pre5-0.3; then
- if which pycentral > /dev/null; then
- pycentral pkgremove bcfg2
- fi
- fi
-fi
-
-#DEBHELPER#
diff --git a/debian/changelog b/debian/changelog
index d6f08a101..2557dd66a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+bcfg2 (1.2.2-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Sat, 17 Mar 2012 14:41:17 -0500
+
+bcfg2 (1.2.1-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Fri, 27 Jan 2012 13:55:45 -0600
+
bcfg2 (1.2.0-0.0) unstable; urgency=low
* New upstream release
diff --git a/debian/control b/debian/control
index 8f4300fee..7835334da 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: admin
Priority: optional
Maintainer: Arto Jantunen <viiru@debian.org>
Uploaders: Sami Haahtinen <ressu@debian.org>
-Build-Depends: debhelper (>= 7.0.50~), python (>= 2.3.5-7)
+Build-Depends: debhelper (>= 7.0.50~), python (>= 2.3.5-7), python-setuptools, python-sphinx (>= 1.0.7+dfsg) | python3-sphinx
Build-Depends-Indep: python-support (>= 0.5.3)
Standards-Version: 3.8.0.0
XS-Python-Version: >= 2.3
@@ -24,7 +24,7 @@ Architecture: all
Depends: ${python:Depends}, ${misc:Depends}, python-lxml (>= 0.9), libxml2-utils (>= 2.6.23), lsb-base (>= 3.1-9), ucf, bcfg2 (= ${binary:Version}), openssl, python-ssl | python2.6 | python3.0 | python3.1 | python3.2, python-gamin
XB-Python-Version: >= 2.4
Recommends: graphviz, patch
-Suggests: python-cheetah, python-genshi (>= 0.4.4), python-profiler, sqlalchemy (>= 0.5.0), python-django, mail-transport-agent
+Suggests: python-cheetah, python-genshi (>= 0.4.4), python-profiler, python-sqlalchemy (>= 0.5.0), python-django, mail-transport-agent, bcfg2-doc (= ${binary:Version})
Description: Configuration management server
Bcfg2 is a configuration management system that generates configuration sets
for clients bound by client profiles.
@@ -33,10 +33,19 @@ Description: Configuration management server
Package: bcfg2-web
Architecture: all
-Depends: ${python:Depends}, ${misc:Depends}, bcfg2 (= ${binary:Version}), python-django,
+Depends: ${python:Depends}, ${misc:Depends}, bcfg2-server (= ${binary:Version}), python-django,
Suggests: python-mysqldb, python-psycopg2, python-sqlite, libapache2-mod-wsgi
XB-Python-Version: >= 2.4
Description: Configuration management web interface
Bcfg2 is a configuration management system that generates configuration sets
for clients bound by client profiles.
bcfg2-web is the reporting server for bcfg2.
+
+Package: bcfg2-doc
+Architecture: all
+Depends: ${sphinxdoc:Depends}, ${misc:Depends}
+XB-Python-Version: >= 2.4
+Description: Configuration management system documentation
+ Bcfg2 is a configuration management system that generates configuration sets
+ for clients bound by client profiles.
+ bcfg2-doc is the documentation for bcfg2.
diff --git a/debian/rules b/debian/rules
index 1638b8415..30fd64a43 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,9 +1,7 @@
#!/usr/bin/make -f
-WSGI_LOC = $(shell find debian/bcfg2-server/ -name reports.wsgi | perl -p -e 's|debian/bcfg2-server||')
-
%:
- dh --with python-support $@
+ dh $@ --with python-support,sphinxdoc
override_dh_auto_install:
# Make the build destination dir consistent between pre-7.3 and 7.3 and
@@ -18,3 +16,10 @@ override_dh_installinit:
# Install bcfg2-server initscript without starting it on postinst
dh_installinit --package=bcfg2-server --no-start
+override_dh_installdocs:
+ python setup.py build_sphinx
+ dh_installdocs build/sphinx/html
+
+override_dh_auto_clean:
+ dh_auto_clean
+ rm -rf build
diff --git a/doc/_templates/indexsidebar.html b/doc/_templates/indexsidebar.html
new file mode 100644
index 000000000..39916315d
--- /dev/null
+++ b/doc/_templates/indexsidebar.html
@@ -0,0 +1,11 @@
+<!-- FIXME: Add download page with pdf/html/txt archives of these documents
+ <h3>Download</h3>
+ <p><a href="{{ pathto('download') }}">Download these documents</a></p>
+-->
+
+ <h3>Docs for other versions</h3>
+ <ul>
+ <li><a href="http://docs.bcfg2.org/1.1/">Bcfg2 1.1 (stable)</a></li>
+ <li><a href="http://docs.bcfg2.org/1.2/">Bcfg2 1.2 (stable)</a></li>
+ <li><a href="http://docs.bcfg2.org/dev/">Bcfg2 development documentation</a></li>
+ </ul>
diff --git a/doc/appendix/guides/centos.txt b/doc/appendix/guides/centos.txt
index a4be1a6d9..50334ccbc 100644
--- a/doc/appendix/guides/centos.txt
+++ b/doc/appendix/guides/centos.txt
@@ -4,43 +4,31 @@
.. _appendix-guides-centos:
-===========================================
-CentOS, Scientific Linux, other RHEL clones
-===========================================
+=====================
+Quickstart for CentOS
+=====================
-This is a complete getting started guide for CentOS, Scientific Linux, other
-Red Hat Enterprise Linux clones. With this document you should be able to
-install and configure a Bcfg2 server and a Bcfg2 client.
+This is a complete getting started guide for CentOS. With this document
+you should be able to install a Bcfg2 server and a Bcfg2 client.
Install Bcfg2
=============
-The fastest way to get Bcfg2 onto your system is to use Yum or your preferred
-package management tool. In this quide the packages that are distributed
-through EPEL_, but depending on your aversion to risk you could download an
-RPM from other places as well. See
-:ref:`getting_started-using_bcfg2-with-centos` for information about building
-Bcfg2 from source and making your own packages.
+The fastest way to get Bcfg2 onto your system is to use Yum or
+your preferred package management tool. We'll be using the ones
+that are distributed through EPEL_, but depending on your aversion
+to risk you could download an RPM from other places as well. See
+:ref:`getting_started-using_bcfg2-with-centos` for information about
+building Bcfg2 from source and making your own packages.
Using EPEL
----------
-.. warning::
-
- EPEL has outdated versions of the server package for CentOS 5 and
- earlier. This guide is intended to be used with versions 1.0.0 and
- higher. Please consider building a newer RPM if you are following
- this guide.
-
Make sure EPEL_ is a valid repository on your server. The `instructions
<http://fedoraproject.org/wiki/EPEL/FAQ#howtouse>`_ on how to do this
basically say::
-EPEL_ for 5.x ::
- [root@config ~]# rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
-
-EPEL_ for 6.x ::
- [root@config ~]# rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-5.noarch.rpm
+ [root@centos ~]# rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm
.. note::
@@ -49,7 +37,7 @@ EPEL_ for 6.x ::
Install the bcfg2-server and bcfg2 RPMs::
- [root@config ~]# yum install bcfg2-server bcfg2
+ [root@centos ~]# yum install bcfg2-server bcfg2
Your system should now have the necessary software to use Bcfg2. The
next step is to set up your Bcfg2 :term:`repository`.
@@ -65,8 +53,8 @@ is a tool which allows you to automate this::
Store bcfg2 configuration in [/etc/bcfg2.conf]:
Location of bcfg2 repository [/var/lib/bcfg2]:
Input password used for communication verification (without echoing; leave blank for a random):
- What is the server's hostname: [config.your.network]
- Input the server location [https://config.your.network:6789]:
+ What is the server's hostname: [centos]
+ Input the server location [https://centos:6789]:
Input base Operating System for clients:
1: Redhat/Fedora/RHEL/RHAS/Centos
2: SUSE/SLES
@@ -82,7 +70,7 @@ is a tool which allows you to automate this::
writing new private key to '/etc/bcfg2.key'
-----
Signature ok
- subject=/C=US=ST=Illinois/L=Argonne/CN=config.your.network
+ subject=/C=US=ST=Illinois/L=Argonne/CN=centos
Getting Private key
Repository created successfuly in /var/lib/bcfg2
@@ -114,20 +102,20 @@ Run bcfg2 to be sure you are able to communicate with the server::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Phase: initial
- Correct entries: 0
- Incorrect entries: 0
- Total managed entries: 0
- Unmanaged entries: 208
+ Correct entries: 0
+ Incorrect entries: 0
+ Total managed entries: 0
+ Unmanaged entries: 208
Phase: final
- Correct entries: 0
- Incorrect entries: 0
- Total managed entries: 0
- Unmanaged entries: 208
+ Correct entries: 0
+ Incorrect entries: 0
+ Total managed entries: 0
+ Unmanaged entries: 208
No ca is specified. Cannot authenticate the server with SSL.
@@ -159,20 +147,20 @@ Now if you run the client, no more warning::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Phase: initial
- Correct entries: 0
- Incorrect entries: 0
- Total managed entries: 0
- Unmanaged entries: 208
+ Correct entries: 0
+ Incorrect entries: 0
+ Total managed entries: 0
+ Unmanaged entries: 208
Phase: final
- Correct entries: 0
- Incorrect entries: 0
- Total managed entries: 0
- Unmanaged entries: 208
+ Correct entries: 0
+ Incorrect entries: 0
+ Total managed entries: 0
+ Unmanaged entries: 208
Bring your first machine under Bcfg2 control
============================================
@@ -185,7 +173,7 @@ Setup the :ref:`server-plugins-generators-packages` plugin
----------------------------------------------------------
First, replace **Pkgmgr** with **Packages** in the plugins
-line of ``bcfg2.conf``. Then create Packages layout (as per
+line of ``bcfg2.conf``. Then create Packages layout (as per
:ref:`packages-exampleusage`) in ``/var/lib/bcfg2``
.. note:: I am using the RawURL syntax here since we are using `mrepo`_
@@ -303,30 +291,30 @@ Now if we run the client, we can see what this has done for us.::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Package pam failed verification.
Phase: initial
- Correct entries: 94
- Incorrect entries: 1
- Total managed entries: 95
- Unmanaged entries: 113
+ Correct entries: 94
+ Incorrect entries: 1
+ Total managed entries: 95
+ Unmanaged entries: 113
In dryrun mode: suppressing entry installation for:
Package:pam
Phase: final
- Correct entries: 94
- Incorrect entries: 1
+ Correct entries: 94
+ Incorrect entries: 1
Package:pam
- Total managed entries: 95
- Unmanaged entries: 113
+ Total managed entries: 95
+ Unmanaged entries: 113
Interesting, our **pam** package failed verification. What does this
mean? Let's have a look::
[root@centos ~]# rpm --verify pam
- ....L... c /etc/pam.d/system-auth
+ ....L... c /etc/pam.d/system-auth
Sigh, it looks like the default RPM install for pam fails to verify
using its own verification process (trust me, it's not the only one). At
@@ -348,23 +336,23 @@ entries?::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Extra Package openssh-clients 4.3p2-36.el5_4.4.x86_64.
Extra Package libuser 0.54.7-2.1el5_4.1.x86_64.
...
Phase: initial
- Correct entries: 95
- Incorrect entries: 0
- Total managed entries: 95
- Unmanaged entries: 113
+ Correct entries: 95
+ Incorrect entries: 0
+ Total managed entries: 95
+ Unmanaged entries: 113
Phase: final
- Correct entries: 95
- Incorrect entries: 0
- Total managed entries: 95
- Unmanaged entries: 113
+ Correct entries: 95
+ Incorrect entries: 0
+ Total managed entries: 95
+ Unmanaged entries: 113
Package:at
Package:avahi
Package:avahi-compat-libdns_sd
@@ -406,22 +394,22 @@ package::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Extra Package gpg-pubkey e8562897-459f07a4.None.
Extra Package gpg-pubkey 217521f6-45e8a532.None.
Phase: initial
- Correct entries: 187
- Incorrect entries: 0
- Total managed entries: 187
- Unmanaged entries: 16
+ Correct entries: 187
+ Incorrect entries: 0
+ Total managed entries: 187
+ Unmanaged entries: 16
Phase: final
- Correct entries: 187
- Incorrect entries: 0
- Total managed entries: 187
- Unmanaged entries: 16
+ Correct entries: 187
+ Incorrect entries: 0
+ Total managed entries: 187
+ Unmanaged entries: 16
Package:gpg-pubkey
Service:atd
Service:avahi-daemon
@@ -574,20 +562,20 @@ Now we run the client and see there are no more unmanaged entries!::
Excluding Packages in global exclude list
Finished
Loaded tool drivers:
- Action Chkconfig POSIX YUMng
+ Action Chkconfig POSIX YUMng
Phase: initial
- Correct entries: 205
- Incorrect entries: 0
- Total managed entries: 205
- Unmanaged entries: 0
+ Correct entries: 205
+ Incorrect entries: 0
+ Total managed entries: 205
+ Unmanaged entries: 0
Phase: final
- Correct entries: 205
- Incorrect entries: 0
- Total managed entries: 205
- Unmanaged entries: 0
+ Correct entries: 205
+ Incorrect entries: 0
+ Total managed entries: 205
+ Unmanaged entries: 0
.. warning::
diff --git a/doc/appendix/guides/fedora.txt b/doc/appendix/guides/fedora.txt
index 9d11414ef..f8dea2192 100644
--- a/doc/appendix/guides/fedora.txt
+++ b/doc/appendix/guides/fedora.txt
@@ -155,8 +155,6 @@ The ``bcfg2.conf`` file contains only standard plugins so far.
database_host =
# Not used with sqlite3.
database_port =
- # Set to empty string for default. Not used with sqlite3.
- web_debug = True
[communication]
protocol = xmlrpc/ssl
diff --git a/doc/appendix/guides/gentoo.txt b/doc/appendix/guides/gentoo.txt
index d635e310b..da4acef19 100644
--- a/doc/appendix/guides/gentoo.txt
+++ b/doc/appendix/guides/gentoo.txt
@@ -16,28 +16,38 @@ let the list know if you find errors or omissions.
Installing Bcfg2
================
-Early in July 2008, Bcfg2 was added to the Gentoo portage tree. So far
-it's only keyworded for ~x86, but we hope to see it soon in the amd64 and
-x64-solaris ports. If you're using Gentoo on some other architecture, it
-should still work provided that you have a reasonably up to date Python;
-try adding `app-admin/bcfg2 ~*` to your `/etc/portage/package.keywords`
-file.
+Early in July 2008, Bcfg2 was added to the Gentoo portage tree.
If you don't use portage to install Bcfg2, you'll want to make sure you
have all the prerequisites installed first. For a server, you'll need:
-* ``app-admin/gamin`` or ``app-admin/fam``
+* ``dev-libs/libgamin[python]``
* ``dev-python/lxml``
Clients will need at least:
* ``app-portage/gentoolkit``
+Portage installs from source
+============================
+
+.. versionadded:: 1.3.0
+
+By default the client will run with the ``--gitbinpkgonly`` option. If
+you want your client to install packages from source (rather than
+having a binary build host as seen below), you can set the following in
+``/etc/bcfg2.conf``.::
+
+ [Portage]
+ binpkgonly = false
+
Package Repository
==================
+.. note: This is only necessary for using binary packages.
+
You’ll need (to make) at least one archive of binary packages. The
-Portage driver calls ``emerge`` with the ``-getbinpkgonly`` option. See
+Portage driver calls ``emerge`` with the ``--getbinpkgonly`` option. See
:manpage:`make.conf(5)` and :manpage:`emerge(1)` manpages, specifically
the :envvar:`PORTAGE_BINHOST` environment variable.
@@ -109,60 +119,17 @@ Configuring Client Machines
Set up ``/etc/bcfg2.conf`` the way you would for any other Bcfg2 client.
In ``make.conf``, set *PORTAGE_BINHOST* to point to the URI of
-your package repository. You may want to create versions of
+your package repository. You may want to create versions of
``make.conf`` for each package repository you maintain, with
appropriate *PORTAGE_BINHOST* URI's in each, and associated with
that package archive's group under ``Cfg`` -- for example, we have
-``Cfg/etc/make.conf/make.conf.G99_gentoo-200701-vmware``. If a client
+``Cfg/etc/make.conf/make.conf.G99_gentoo-200701-vmware``. If a client
host switches groups, and the new group needs a different set of packages,
everything should just fall into place.
Pitfalls
========
-Package Verification Issues
----------------------------
-
-As of this writing (2007/01/31), we're aware of a number of packages
-marked stable in the Gentoo x86 tree which, for one reason or another,
-consistently fail to verify cleanly under ``equery check``. In some cases
-(pam, openldap), files which don't (ever) exist on the system are
-nonetheless recorded in the package database; in some (python, Bcfg2,
-ahem), whole classes of files (.pyc and .pyo files) consistently fail
-their md5sum checks; and in others, the problem appears to be a
-discrepancy in the way that symlinks are created vs. the way they're
-recorded in the database. For example, in the OpenSSH package,
-/usr/bin/slogin is a symlink to ./ssh, but equery expects it to point to
-an unadorned ssh. An analogous situation exists with their manpages,
-leading to noise like this::
-
- # equery check openssh
- [ Checking net-misc/openssh-4.5_p1 ]
- !!! /etc/ssh/sshd_config has incorrect md5sum
- !!! /usr/bin/slogin does not point to ssh
- !!! /usr/share/man/man1/slogin.1.gz does not point to ssh.1.gz
- !!! /etc/ssh/ssh_config has incorrect md5sum
- * 62 out of 66 files good
-
-We can ignore the lines for ``ssh_config`` and ``sshd_config``; those will
-be caught by Bcfg2 as registered config files and handled appropriately.
-
-Because Bcfg2 relies on the client system's native package reporting
-tool to judge the state of installed packages, complaints like these
-about trivial or intractable verification failures can trigger unnecessary
-bundle reinstalls when the Bcfg2 client runs. Bcfg2 will catch on after a
-pass or two that the situation isn't getting any better with repeated
-package installs, stop trying, and list those packages as "bad" in
-the client system's statistics.
-
-Aside from filing bugs with the Gentoo package maintainers, your narrator
-has been unable to come up with a good approach to this. Maybe write a
-series of ``Rules`` definitions according to what the package database
-thinks it should find, and/or stage copies of affected files under
-``Cfg``, and associate those rules and files with the affected package in
-a bundle? Annoying but possibly necessary if you want your stats file
-to look good.
-
/boot
-----
diff --git a/doc/appendix/guides/ubuntu.txt b/doc/appendix/guides/ubuntu.txt
index fe5564d19..f72247220 100644
--- a/doc/appendix/guides/ubuntu.txt
+++ b/doc/appendix/guides/ubuntu.txt
@@ -133,8 +133,6 @@ Replace Pkgmgr with Packages in the plugins line of ``bcfg2.conf``::
database_host =
# Not used with sqlite3.
database_port =
- # Set to empty string for default. Not used with sqlite3.
- web_debug = True
[communication]
protocol = xmlrpc/ssl
@@ -156,7 +154,7 @@ Create Packages layout (as per :ref:`packages-exampleusage`) in
[global]
root@lucid:~# cat /var/lib/bcfg2/Packages/sources.xml
<Sources>
- <Group name="lucid">
+ <Group name="ubuntu-lucid">
<Source type="apt" url="http://archive.ubuntu.com/ubuntu" version="lucid">
<Component>main</Component>
<Component>multiverse</Component>
diff --git a/doc/appendix/guides/web-reports-install.txt b/doc/appendix/guides/web-reports-install.txt
index 7ec7efb4e..f6a588692 100644
--- a/doc/appendix/guides/web-reports-install.txt
+++ b/doc/appendix/guides/web-reports-install.txt
@@ -176,8 +176,6 @@ then have something like this::
database_host =
# Not used with sqlite3.
database_port =
- # Set to empty string for default. Not used with sqlite3.
- web_debug = True
Restart apache and point a browser to your Bcfg2 server.
diff --git a/doc/client/tools/yumng.txt b/doc/client/tools/yumng.txt
index c2e9161a1..54003aea1 100644
--- a/doc/client/tools/yumng.txt
+++ b/doc/client/tools/yumng.txt
@@ -142,7 +142,8 @@ To compile and install prelink, execute::
in the rpmtools directory. The elfutils-libelf-devel package is required
for the compilation.
-There are Centos x86_64 RPMs here ftp://ftp.mcs.anl.gov/pub/bcfg/redhat/
+There are Centos x86_64 RPMs here
+ftp://ftp.mcs.anl.gov/pub/bcfg/archive/redhat/
Configuration and Usage
=======================
diff --git a/doc/conf.py b/doc/conf.py
index 96d2d715d..5903b009a 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -55,7 +55,7 @@ else:
# The short X.Y version.
version = '1.2'
# The full version, including alpha/beta/rc tags.
-release = '1.2.0'
+release = '1.2.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -104,7 +104,9 @@ html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+html_theme_options = {
+ "collapsiblesidebar": "true"
+}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
@@ -139,7 +141,9 @@ html_last_updated_fmt = '%b %d, %Y'
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+html_sidebars = {
+ 'index': 'indexsidebar.html'
+}
# Additional templates that should be rendered to pages, maps page names to
# template names.
diff --git a/doc/development/index.txt b/doc/development/index.txt
index 352000dc8..2a54bfad8 100644
--- a/doc/development/index.txt
+++ b/doc/development/index.txt
@@ -39,3 +39,4 @@ git access. Mail the :ref:`help-mailinglist` for details.
testing
documentation
docstyleguide
+ unit-testing
diff --git a/doc/development/plugins.txt b/doc/development/plugins.txt
index 15b512365..b2b70f553 100644
--- a/doc/development/plugins.txt
+++ b/doc/development/plugins.txt
@@ -164,7 +164,6 @@ Example Connector
Bcfg2.Server.Plugin.Connector):
'''The Foo plugin is here to illustrate a barebones connector'''
name = 'Foo'
- version = '$Revision: $'
experimental = True
def __init__(self, core, datastore):
@@ -195,13 +194,10 @@ do so. We will call our new plugin `MyMetadata`.
.. code-block:: python
- __revision__ = '$Revision$'
-
import Bcfg2.Server.Plugins.Metadata
class MyMetadata(Bcfg2.Server.Plugins.Metadata.Metadata):
'''This class contains data for bcfg2 server metadata'''
- __version__ = '$Id$'
__author__ = 'bcfg-dev@mcs.anl.gov'
def __init__(self, core, datastore, watch_clients=True):
diff --git a/doc/development/setup.txt b/doc/development/setup.txt
index e9fc6e1e5..b04bce3fe 100644
--- a/doc/development/setup.txt
+++ b/doc/development/setup.txt
@@ -12,13 +12,8 @@ Checking Out a Copy of the Code
git clone git://git.mcs.anl.gov/bcfg2.git
-* Create link to :file:`src/lib`::
-
- cd bcfg2
- ln -s src/lib Bcfg2
-
* Add :file:`bcfg2/src/sbin` to your :envvar:`PATH` environment variable
-* Add :file:`bcfg2` to your :envvar:`PYTHONPATH` environment variable
+* Add :file:`bcfg2/src/lib` to your :envvar:`PYTHONPATH` environment variable
Using a Virtual Environment for Development
diff --git a/doc/development/unit-testing.txt b/doc/development/unit-testing.txt
new file mode 100644
index 000000000..30217dcc5
--- /dev/null
+++ b/doc/development/unit-testing.txt
@@ -0,0 +1,25 @@
+.. -*- mode: rst -*-
+
+.. _development-unit-testing:
+
+==================
+Bcfg2 unit testing
+==================
+
+.. _Python Mock Module: http://python-mock.sourceforge.net/
+.. _Python Nose: http://readthedocs.org/docs/nose/en/latest/
+
+You will first need to install the `Python Mock Module`_ and `Python
+Nose`_ modules. You can then run the existing tests with the
+following.::
+
+ cd testsuite
+ nosetests
+
+You should see output something like the following::
+
+ ..................................................
+ ----------------------------------------------------------------------
+ Ran 50 tests in 0.121s
+
+ OK
diff --git a/doc/glossary.txt b/doc/glossary.txt
index 5455e9ced..06f67dab9 100644
--- a/doc/glossary.txt
+++ b/doc/glossary.txt
@@ -33,8 +33,6 @@ Glossary
profile
A special type of group that a client is explicitly assigned to.
- structure
-
repository
A collection of folders and files that together define the
configurations that Bcfg2 applies to clients. The repository
diff --git a/doc/installation/source.txt b/doc/installation/source.txt
index 3ea0404ad..1406a5ceb 100644
--- a/doc/installation/source.txt
+++ b/doc/installation/source.txt
@@ -2,6 +2,7 @@
.. _GPG1: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x75BF2C177F7D197E
.. _GPG2: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x80B8492FA88FFF4B
+.. _Download: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Download
.. _source:
@@ -14,7 +15,7 @@ Download
Tarball
^^^^^^^
-The Bcfg2 source tarball can be grabbed from the Download_ page.
+The Bcfg2 source tarball can be grabbed from the `Download`_ page.
All tarballs are signed with GPG keys `7F7D197E <GPG1>`_ or `A88FFF4B
<GPG2>`_. You can verify your download by importing the keys and running ::
diff --git a/doc/reports/dynamic.txt b/doc/reports/dynamic.txt
index 4c75cce32..07763922c 100644
--- a/doc/reports/dynamic.txt
+++ b/doc/reports/dynamic.txt
@@ -71,6 +71,15 @@ Apache configuration for web-based reports
by adding a **web_prefix** setting in the [statistics] section of
your ``bcfg2.conf``.
+.. warning::
+
+ When running with SELINUX enabled, you can have potential problems
+ with the WSGISocketPrefix. One solution that works without too much
+ trouble is modifying your prefix so that it is located in a standard
+ location::
+
+ WSGISocketPrefix /var/run/httpd/wsgi
+
An example site config is included below::
<IfModule mod_wsgi.c>
diff --git a/doc/server/plugins/connectors/templatehelper.txt b/doc/server/plugins/connectors/templatehelper.txt
new file mode 100644
index 000000000..24d7f18b5
--- /dev/null
+++ b/doc/server/plugins/connectors/templatehelper.txt
@@ -0,0 +1,74 @@
+.. -*- mode: rst -*-
+
+.. _server-plugins-connectors-templatehelper:
+
+==============
+TemplateHelper
+==============
+
+The TemplateHelper plugin is a connector plugin that adds Python
+classes and methods to client metadata instances for use in
+templates. This allows you to easily reuse code that is common
+amongst multiple templates and add convenience methods.
+
+Using TemplateHelper
+====================
+
+First, ``mkdir /var/lib/bcfg2/TemplateHelper`` and add
+**TemplateHelper** to your ``plugins`` line in ``/etc/bcfg2.conf``.
+Restart ``bcfg2-server``.
+
+Now, any ``.py`` file placed in ``/var/lib/bcfg2/TemplateHelper/``
+will be read and added to matching client metadata objects. See
+:ref:`writing-templatehelpers` below for more information on how to
+write TemplateHelper scripts.
+
+TemplateHelper supports group- and host-specific helpers, so you could
+create, e.g., ``foo.py.G80_test`` to create a helper that only applied
+to machines in the group ``test``.
+
+.. _writing-templatehelpers:
+
+Writing Helpers
+===============
+
+A helper module is just a Python module with three special conditions:
+
+* The filename must end with ``.py`` (before any specificity
+ strings, e.g., ``.G80_foo`` or ``.H_blah.example.com``
+* The module must have an attribute, ``__export__``, that lists all of
+ the classes, functions, variables, or other symbols you wish to
+ export from the module.
+* ``data``, ``handle_event``, ``name``, and ``specific`` are reserved
+ names. You should not include symbols with a reserved name in
+ ``__export__``. Additionally, including symbols that start with an
+ underscore or double underscore is bad form, and may also produce
+ errors.
+
+See ``examples/TemplateHelper`` for examples of helper modules.
+
+Usage
+=====
+
+Specific helpers can be referred to in
+templates as ``metadata.TemplateHelper[<modulename>]``. That accesses
+a HelperModule object will has, as attributes, all symbols listed in
+``__export__``. For example, consider this helper module::
+
+ __export__ = ["hello"]
+
+ def hello(metadata):
+ return "Hello, %s!" % metadata.hostname
+
+To use this in a TGenshi template, we could do::
+
+ ${metadata.TemplateHelper['hello'].hello(metadata)}
+
+The template would produce::
+
+ Hello, foo.example.com!
+
+Note that the client metadata object is not passed to a helper module
+in any magical way; if you want to access the client metadata object
+in a helper function or class, you must pass the object to the
+function manually.
diff --git a/doc/server/plugins/generators/packages.txt b/doc/server/plugins/generators/packages.txt
index 555f7ac97..42efd35a1 100644
--- a/doc/server/plugins/generators/packages.txt
+++ b/doc/server/plugins/generators/packages.txt
@@ -8,11 +8,12 @@ Packages
.. versionadded:: 1.0.0
-Packages is an alternative to :ref:`Pkgmgr <server-plugins-generators-pkgmgr>`
- for specifying package entries for clients. Where Pkgmgr explicitly
-specifies package entry information, Packages delegates control of package
-version information to the underlying package manager, installing the latest
-version available through those channels.
+This page documents the Packages plugin. Packages is an alternative to
+:ref:`Pkgmgr <server-plugins-generators-pkgmgr>` for specifying package
+entries for clients. Where Pkgmgr explicitly specifies package entry
+information, Packages delegates control of package version information to
+the underlying package manager, installing the latest version available
+through those channels.
.. _server-plugins-generators-packages-magic-groups:
@@ -33,29 +34,29 @@ member clients.
+--------+----------+--------------+
| Source | OS Group | Architecture |
+========+==========+==============+
-| Apt | debian | i386 |
+| Apt | debian | i386 |
+--------+----------+--------------+
-| Apt | ubuntu | amd64 |
+| Apt | ubuntu | amd64 |
+--------+----------+--------------+
-| Apt | nexenta | |
+| Apt | nexenta | |
+--------+----------+--------------+
-| Apt | apt | |
+| Apt | apt | |
+--------+----------+--------------+
-| Yum | redhat | i386 |
+| Yum | redhat | i386 |
+--------+----------+--------------+
-| Yum | centos | x86_64 |
+| Yum | centos | x86_64 |
+--------+----------+--------------+
-| Yum | fedora | |
+| Yum | fedora | |
+--------+----------+--------------+
-| Yum | yum | |
+| Yum | yum | |
+--------+----------+--------------+
-.. note::
+.. note::
.. versionadded:: 1.2.0
Magic OS groups can be disabled in Bcfg2 1.2 and greater by setting
- ``magic_groups`` to ``0`` in ``Packages/packages.conf``. This may
+ ``magic_groups`` to ``0`` in ``Packages/packages.conf``. This may
give you greater flexibility in determining which source types to
use for which OSes. Magic architecture groups cannot be disabled.
@@ -64,10 +65,10 @@ Limiting sources to groups
==========================
``Packages/sources.xml`` processes ``<Group>`` and ``<Client>`` tags
-just like Bundles. In addition to any groups or clients specified
+just like Bundles. In addition to any groups or clients specified
that way, clients must
be a member of the appropriate architecture group as specified in a
-Source stanza. In total, in order for a source to be associated with
+Source stanza. In total, in order for a source to be associated with
a client, the client must be in one of the magic groups (debian,
ubuntu, or nexenta), any explicit groups or clients specified in
``sources.xml``, and any specified architecture groups.
@@ -129,12 +130,12 @@ Dependency resolution can be disabled by adding this to
``Packages/packages.conf`` in the ``global`` section::
[global]
- resolver=disabled
+ resolver=0
All metadata processing can be disabled as well::
[global]
- metadata=disabled
+ metadata=0
Blacklisting faulty dependencies
--------------------------------
@@ -145,7 +146,7 @@ future releases. In the meantime, you can work around this issue by
blacklisting the offending Package in your Sources. The blacklist
element should immediately follow the Component section of your source
and should look like the following::
- .. code-block:: xml
+
<Blacklist>unwanted-packagename</Blacklist>
If you use the built-in :ref:`Yum config generator
@@ -157,10 +158,12 @@ Handling GPG Keys
.. versionadded:: 1.2.0
-Packages can automatically handle GPG signing keys for Yum and Pulp
-repositories. Simply specify the URL to the GPG key(s) for a
-repository in ``sources.xml``::
- .. code-block:: xml
+If you have yum libraries installed, Packages can automatically handle
+GPG signing keys for Yum and Pulp repositories. (You do not need to
+use the native yum resolver; if yum libraries are available, GPG
+signing keys can be handled automatically.) Simply specify the URL to
+the GPG key(s) for a repository in ``sources.xml``::
+
<Source type="yum"
rawurl="http://mirror.example.com/centos6-x86_64/RPMS.os">
<Arch>x86_64</Arch>
@@ -182,11 +185,9 @@ REST API.
Example usage
=============
-APT
----
Create a ``sources.xml`` file in the Packages directory that looks
something like this::
- .. code-block:: xml
+
<Sources>
<Group name="ubuntu-intrepid">
<Source type="apt"
@@ -204,24 +205,31 @@ something like this::
.. versionadded:: 1.1.0
- The default behavior of the Packages plugin is to not make
- any assumptions about which packages you want to have added
- automatically. For that reason, neither **Recommended** nor
- **Suggested** packages are added as dependencies by default. You
- will notice that the default behavior for apt is to add Recommended
- packages as dependencies. You can configure the Packages plugin to
- add recommended packages by adding the ``recommended`` attribute,
- e.g.:
+ The default behavior of the Packages plugin is to not make any
+ assumptions about which packages you want to have added automatically
+ [#f1]_. For that reason, neither **Recommended** nor **Suggested**
+ packages are added as dependencies by default. You will notice
+ that the default behavior for apt is to add Recommended packages as
+ dependencies. You can configure the Packages plugin to add recommended
+ packages by adding the ``recommended`` attribute, e.g.:
.. code-block:: xml
<Source type="apt" recommended="true" ...>
-YUM
----
+ .. warning:: You must regenerate the Packages cache when adding or
+ removing the recommended attribute.
+
+ .. [#f1] Bcfg2 will by default add **Essential** packages to the
+ client specification. You can disable this behavior by
+ setting the ``essential`` attribute to *false*:
-Yum sources can be similarly specified::
.. code-block:: xml
+
+ <Source type="apt" essential="false" ...>
+
+Yum sources can be similarly specified::
+
<Sources>
<Group name="centos-5.2">
<Source type="yum"
@@ -240,13 +248,9 @@ Yum sources can be similarly specified::
For sources with a **URL** attribute, the **Version** attribute is
also necessary.
-Pulp
-----
-
:ref:``Pulp sources <pulp-source-support>`` are very simple to specify
due to the amount of data that can be queried from Pulp itself::
- .. code-block:: xml
<Sources>
<Group name="centos-6-x86_64">
<Source type="yum" pulp_id="centos-6-x86_64-os"/>
@@ -255,19 +259,18 @@ due to the amount of data that can be queried from Pulp itself::
</Group>
</Sources>
-Raw URLs
---------
-For specifying sources that don't follow the conventional layout, the rawurl
-attribute is useful::
+.. note:: There is also a rawurl attribute for specifying sources that
+ don't follow the conventional layout.
- .. code-block:: xml
- <Sources>
- <Group name="centos5.4">
- <Source type="yum"
- rawurl="http://mrepo.ices.utexas.edu/centos5-x86_64/RPMS.os">
- <Arch>x86_64</Arch>
- </Source>
- <Source type="yum"
+ .. code-block:: xml
+
+ <Sources>
+ <Group name="centos5.4">
+ <Source type="yum"
+ rawurl="http://mrepo.ices.utexas.edu/centos5-x86_64/RPMS.os">
+ <Arch>x86_64</Arch>
+ </Source>
+ <Source type="yum"
rawurl="http://mrepo.ices.utexas.edu/centos5-x86_64/RPMS.updates">
<Arch>x86_64</Arch>
</Source>
@@ -278,19 +281,20 @@ attribute is useful::
</Group>
</Sources>
- .. code-block:: xml
- <Sources>
- <Group name="ubuntu-lucid">
- <Source type="apt"
- rawurl="http://hudson-ci.org/debian/binary">
- <Arch>amd64</Arch>
- </Source>
- <Source type="apt"
- rawurl=http://hudson-ci.org/debian/binary">
- <Arch>i386</Arch>
- </Source>
- </Group>
- </Sources>
+ .. code-block:: xml
+
+ <Sources>
+ <Group name="ubuntu-lucid">
+ <Source type="apt"
+ rawurl="http://hudson-ci.org/debian/binary">
+ <Arch>amd64</Arch>
+ </Source>
+ <Source type="apt"
+ rawurl=http://hudson-ci.org/debian/binary">
+ <Arch>i386</Arch>
+ </Source>
+ </Group>
+ </Sources>
Configuration Updates
=====================
@@ -338,7 +342,7 @@ updated.
Availability
============
-Support for clients using yum and apt is currently available. Support for
+Support for clients using yum and apt is currently available. Support for
other package managers (Portage, Zypper, IPS, etc) remain to be added.
Validation
@@ -354,7 +358,7 @@ Limitations
Packages does not do traditional caching as other plugins
do. Modifying sources in the Packages ``sources.xml`` file requires a
-server restart for the time being. You do not have to restart the
+server restart for the time being. You do not have to restart the
server after changing ``packages.conf`` or after adding new sources to
``sources.xml``.
@@ -364,7 +368,6 @@ Package Checking and Verification
In order to do disable per-package verification Pkgmgr style, you will
need to use :ref:`BoundEntries <boundentries>`, e.g.::
- .. code-block:: xml
<BoundPackage name="mem-agent" priority="1" version="auto"
type="yum" verify="false"/>
@@ -388,9 +391,9 @@ Then add the corresponding Path entry to your Yum bundle.
.. versionadded:: 1.1.0
APT repository information can be generated automatically from
-software sources using :doc:`./tgenshi/index` or :doc:`./tcheetah`. A
+software sources using :doc:`./tgenshi/index` or :doc:`./tcheetah`. A
list of source urls are exposed in the client's metadata as
-``metadata.Packages.sources``. E.g.::
+``metadata.Packages.sources``. E.g.::
# bcfg2 maintained apt
@@ -408,7 +411,7 @@ Using Native Yum Libraries
By default, Bcfg2 uses an internal implementation of Yum's dependency
resolution and other routines so that the Bcfg2 server can be run on a
-host that does not support Yum itself. If you run the Bcfg2 server on
+host that does not support Yum itself. If you run the Bcfg2 server on
a machine that does have Yum libraries, however, you can enable use of
those native libraries in Bcfg2 by setting ``use_yum_libraries`` to
``1`` in the ``[yum]`` section of ``Packages/packages.conf``.
@@ -422,7 +425,7 @@ Benefits to this include:
Drawbacks include:
-* More disk I/O. In some cases, you may have to raise the open file
+* More disk I/O. In some cases, you may have to raise the open file
limit for the user who runs your Bcfg2 server process, particularly
if you have a lot of repositories.
* Resolution of package dependencies is slower in some cases,
@@ -437,8 +440,8 @@ Configuring the Yum Helper
Due to poor memory management by the Yum API, the long-lived
bcfg2-server process uses an external short-lived helper,
``bcfg2-yum-helper``, to do the actual Yum API calls for native yum
-library support. By default, Bcfg2 looks for this helper at
-``/usr/sbin/bcfg2-yum-helper``. If you have installed the helper
+library support. By default, Bcfg2 looks for this helper at
+``/usr/sbin/bcfg2-yum-helper``. If you have installed the helper
elsewhere, you will need to configure that location with the
``helper`` option in the ``[yum]`` section, e.g.::
@@ -452,11 +455,11 @@ Setting Yum Options
In ``Packages/packages.conf``, any options you set in the ``[yum]``
section other than ``use_yum_libraries`` and ``helper`` will be passed
along verbatim to the configuration of the Yum objects used in the
-Bcfg2 server. The following options are set by default, and should
+Bcfg2 server. The following options are set by default, and should
not generally be overridden:
* ``cachedir`` is set to a hashed value unique to each distinct Yum
- configuration. Don't set this unless you know what you're doing.
+ configuration. Don't set this unless you know what you're doing.
* ``keepcache`` is set to ``0``; there is no benefit to changing this.
* ``sslverify`` is set to ``0``; change this if you know what you're
doing.
@@ -466,18 +469,18 @@ not generally be overridden:
Package Groups
--------------
-Yum package groups are supported by the native Yum libraries. To
+Yum package groups are supported by the native Yum libraries. To
include a package group, use the ``group`` attribute of the
-``Package`` tag. You can use either the short group ID or the long
+``Package`` tag. You can use either the short group ID or the long
group name::
- .. code-block:: xml
+
<Package group="SNMP Support"/>
<Package group="system-management-snmp"/>
By default, only those packages considered the "default" packages in a
-group will be installed. You can change this behavior using the
+group will be installed. You can change this behavior using the
"type" attribute::
- .. code-block:: xml
+
<Package group="development" type="optional"/>
<Package group="Administration Tools" type="mandatory"/>
@@ -489,7 +492,7 @@ Valid values of "type" are:
including mandatory, default, and optional packages.
You can view the packages in a group by category with the ``yum
-groupinfo`` command. More information about the different levels can
+groupinfo`` command. More information about the different levels can
be found at
http://fedoraproject.org/wiki/How_to_use_and_edit_comps.xml_for_package_groups#Installation
@@ -501,10 +504,10 @@ Pulp Support
.. versionadded:: 1.2.0
Bcfg2 contains explicit support for repositories managed by Pulp
-(http://pulpproject.org/). Due to the amount of data about a
+(http://pulpproject.org/). Due to the amount of data about a
repository that can be retrieved directly from Pulp, the only thing
necessary to configure a Pulp repo is the repo ID::
- .. code-block:: xml
+
<Sources>
<Group name="centos-6-x86_64">
<Source type="yum" pulp_id="centos-6-x86_64-os"/>
@@ -513,7 +516,7 @@ necessary to configure a Pulp repo is the repo ID::
</Group>
</Sources>
-Pulp sources require some additional configuration. First, the Bcfg2
+Pulp sources require some additional configuration. First, the Bcfg2
server must have a valid ``/etc/pulp/consumer/consumer.conf`` that is
readable by the user your Bcfg2 server runs as; the Pulp server,
URLs, and so on, are determined from this.
@@ -523,7 +526,7 @@ options in the ``[pulp]`` section:
* ``username`` and ``password``: The username and password of a Pulp
user that will be used to register new clients and bind them to
- repositories. Membership in the default ``consumer-users`` role is
+ repositories. Membership in the default ``consumer-users`` role is
sufficient.
Bcfg2 clients using Pulp sources will be registered to the Pulp server
@@ -532,50 +535,50 @@ as consumers, and will be bound to the appropriate repositories.
Debugging unexpected behavior
=============================
+.. versionadded:: 1.2.1
+
Using bcfg2-info
----------------
The dependency resolver used in Packages can be run in debug mode::
-
- $ bcfg2-info
+ $ bcfg2-info packageresolve foo.example.com bcfg2-server zlib
...
- Handled 20 events in 0.004s
- > debug
- dropping to python interpreter; press ^D to resume
- ...
- (debug_shell)
- >>> m = self.build_metadata('ubik3')
- >>> self.plugins['Packages'].complete(m, ['ssh'], debug=True)
- Package ssh: adding new deps ['openssh-client', 'openssh-server']
- Package openssh-server: adding new deps ['libc6', 'libcomerr2', 'libkrb53', 'libpam0g', 'libselinux1', 'libssl0.9.8
- ', 'libwrap0', 'zlib1g', 'debconf', 'libpam-runtime', 'libpam-modules', 'adduser', 'dpkg', 'lsb-base']
- Package debconf: adding new deps ['debconf-i18n']
- Package libpam-modules: adding new deps ['libdb4.7']
- Package openssh-client: adding new deps ['libedit2', 'libncurses5', 'passwd']
- Package lsb-base: adding new deps ['sed', 'ncurses-bin']
- Package adduser: adding new deps ['perl-base']
- Package debconf-i18n: adding new deps ['liblocale-gettext-perl', 'libtext-iconv-perl', 'libtext-wrapi18n-perl', 'libtext-charwidth-perl']
- Package passwd: adding new deps ['debianutils']
- Package libtext-charwidth-perl: adding new deps ['perlapi-5.10.0']
- VPackage perlapi-5.10.0: got provides ['perl-base']
- Package libkrb53: adding new deps ['libkeyutils1']
- Package libtext-iconv-perl: adding new deps ['perlapi-5.10.0']
- Package libc6: adding new deps ['libgcc1', 'findutils']
- Package libgcc1: adding new deps ['gcc-4.3-base']
- (set(['debconf', 'libgcc1', 'lsb-base', 'libtext-wrapi18n-perl', 'libtext-iconv-perl', 'sed', 'passwd', 'findutils', 'libpam0g', 'openssh-client', 'debconf-i18n', 'libselinux1', 'zlib1g', 'adduser', 'libwrap0', 'ncurses-bin', 'libssl0.9.8', 'liblocale-gettext-perl', 'libkeyutils1', 'libpam-runtime', 'libpam-modules', 'openssh-server', 'libkrb53', 'ssh', 'libncurses5', 'libc6', 'libedit2', 'libcomerr2', 'dpkg', 'perl-base', 'libdb4.7', 'libtext-charwidth-perl', 'gcc-4.3-base', 'debianutils']), set([]), 'deb')
+ 2 initial packages
+ bcfg2-server
+ zlib
+ 54 new packages added
+ sqlite
+ less
+ libxml2
+ expat
+ ...
+ 1 unknown packages
+ libglib-2.0.so.0()(64bit)
This will show why the resolver is acting as it is. Replace
-``"ubik3"`` and ``['ssh']`` with a client name and list of packages,
-respectively. Also, a more polished interface to this functionality is
-coming as well.
+``foo.example.com`` and ``bcfg2-server`` with a client name and list
+of packages, respectively.
+
+Note that resolving a partial package list (as above) may result in
+more unknown entries than you'd have otherwise; some of the package
+drivers (Yum in particular) consider the full package list when
+resolving multiple providers, and will not be able to properly resolve
+some dependencies without a full package list.
-Each line starting with Package: <name> describes a set of new
-prerequisites pulled in by a package. Lines starting with VPackage <vname>
-describe provides entries and their mappings to required names. The last
-line describes the overall results of the resolver, with three fields:
-a list of packages that should be installed, a list of unresolved
-requirements, and a type for these packages.
+You can also view the sources applicable to a client::
+
+ $ bcfg2-info packagesources foo.example.com
+ ...
+ Name: centos-6-x86_64-updates
+ Type: yum
+ URL: http://mirror.example.com/centos-6-x86_64-updates
+ GPG Key(s): http://mirror.example.com/centos-6-x86_64-updates/RPM-GPG-KEY-CentOS-6
+
+ Name: centos-6-x86_64-os
+ Type: yum
+ URL: http://mirror.example.com/centos-6-x86_64-os
+ GPG Key(s): http://mirror.example.com/centos-6-x86_64-os/RPM-GPG-KEY-CentOS-6
Using bcfg2-server
------------------
@@ -584,6 +587,13 @@ Once the server is started, enable debugging via bcfg2-admin::
$ bcfg2-admin xcmd Packages.toggle_debug
+TODO list
+=========
+
+* Zypper support
+* Portage support
+* Explicit version pinning (a la Pkgmgr)
+
Developing for Packages
=======================
@@ -637,37 +647,36 @@ packages.conf
=============
``packages.conf`` contains miscellaneous configuration options for the
-Packages plugin. It understands the following directives:
+Packages plugin. Any booleans in the config file accept the values
+"1", "yes", "true", and "on" for True, and "0", "no", "false", and
+"off" for False
+
+It understands the following directives:
[global] section
----------------
-* ``resolver``: Disable dependency resolution. Default is "enabled".
-* ``metadata``: Disable metadata processing. Default is "enabled".
-* ``yum_config``: The path at which to generate Yum configs. No
+* ``resolver``: Enable dependency resolution. Default is ``1``
+ (true). For historical reasons, this also accepts "enabled" and
+ "disabled".
+* ``metadata``: Enable metadata processing. Default is ``1``
+ (true). For historical reasons, this also accepts "enabled" and
+ "disabled".
+* ``yum_config``: The path at which to generate Yum configs. No
default.
-* ``apt_config``: The path at which to generate APT configs. No
+* ``apt_config``: The path at which to generate APT configs. No
default.
* ``gpg_keypath``: The path on the client RPM GPG keys will be copied
- to before they are imported on the client. Default is
+ to before they are imported on the client. Default is
"/etc/pki/rpm-gpg".
-* ``import_gpg_keys``: The RPM release of an RPM GPG key cannot be
- reliably and automatically determined without importing the key into
- the server's key chain. If ``import_gpg_keys`` is "false" (the
- default), the release of automatically-generated RPM GPG key entries
- in the specification will be set to "any", which disables
- verification of the release. (Version will still be verified.) In
- practice, this is unlikely to be an issue, as the RPM version of a
- GPG key is the key's fingerprint, and collisions are rare. If you
- do encounter a GPG key version collision, you will need to set this
- to "true", whereupon Packages will import the keys into the server's
- key chain. Python RPM libraries must be installed for this to work.
+* ``version``: Set the version attribute used when binding
+ Packages. Default is ``auto``.
[yum] section
-------------
* ``use_yum_libraries``: Whether or not to use the :ref:`native yum
- library support <native-yum-libraries>`. Default is ``0`` (false).
+ library support <native-yum-libraries>`. Default is ``0`` (false).
All other options in the ``[yum]`` section will be passed along
verbatim to the Yum configuration if you are using the native Yum
@@ -678,5 +687,5 @@ library support.
* ``username`` and ``password``: The username and password of a Pulp
user that will be used to register new clients and bind them to
- repositories. Membership in the default ``consumer-users`` role is
+ repositories. Membership in the default ``consumer-users`` role is
sufficient.
diff --git a/doc/server/plugins/generators/rules.txt b/doc/server/plugins/generators/rules.txt
index 925ee6419..c084c5681 100644
--- a/doc/server/plugins/generators/rules.txt
+++ b/doc/server/plugins/generators/rules.txt
@@ -68,7 +68,7 @@ The Rules Group Tag may have the following attributes:
+========+=========================+==============+
| name | Group Name | String |
+--------+-------------------------+--------------+
-| negate | Negate group membership | (True|False) |
+| negate | Negate group membership | (true|false) |
| | (is not a member of) | |
+--------+-------------------------+--------------+
@@ -195,7 +195,7 @@ The Client Tag may have the following attributes:
+========+=========================+==============+
| name | Client Name | String |
+--------+-------------------------+--------------+
-| negate | Negate client selection | (True|False) |
+| negate | Negate client selection | (true|false) |
| | (if not client name) | |
+--------+-------------------------+--------------+
@@ -354,8 +354,18 @@ how to assign Rules to a host's literal configuration.
Using Regular Expressions in Rules
==================================
-The ``name`` attribute in Rules supports the use of regular
-expressions to match multiple abstract configuration entries.
+If you wish, you can configure the Rules plugin to support regular
+expressions. This entails a small performance and memory usage
+penalty. To do so, create a file, "Rules/rules.conf", and add the
+following text::
+
+ [rules]
+ regex = yes
+
+You will have to restart the Bcfg2 server after making that change.
+
+With regular expressions enabled, you can use a regex in the ``name``
+attribute to match multiple abstract configuration entries.
Regular expressions are anchored at both ends, so ``<Service
name="bcfg2".../>`` will *not* match a Service named ``bcfg2-server``;
diff --git a/doc/server/plugins/generators/tgenshi/index.txt b/doc/server/plugins/generators/tgenshi/index.txt
index c5392dcc4..21ef8f17f 100644
--- a/doc/server/plugins/generators/tgenshi/index.txt
+++ b/doc/server/plugins/generators/tgenshi/index.txt
@@ -130,7 +130,7 @@ Then, run::
setup = Bcfg2.Options.OptionParser({'repo':
Bcfg2.Options.SERVER_REPOSITORY})
setup.parse('--')
- template = TemplateLoader().load(set['repo'] + path, cls=NewTextTemplate)
+ template = TemplateLoader().load(setup['repo'] + path, cls=NewTextTemplate)
print template.generate(metadata=metadata, path=path, name=name).render()
This gives you more fine-grained control over how your template is
diff --git a/doc/server/plugins/generators/tgenshi/iptables.txt b/doc/server/plugins/generators/tgenshi/iptables.txt
index 2655e7b2d..310f9ffab 100644
--- a/doc/server/plugins/generators/tgenshi/iptables.txt
+++ b/doc/server/plugins/generators/tgenshi/iptables.txt
@@ -83,13 +83,14 @@ iptables
-A NO-SMTP -j DROP
# Allow SSH Access
- -A INPUT -p tcp -m state --state NEW -m tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 22 -j SSH
- -A SSH -s 192.0.0.0/255.0.0.0 -j ACCEPT
+ :SSH - [0:0]
+ -A INPUT -p tcp -m state --state NEW -m tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 22 -j SSH
+ -A SSH -s 192.168.0.0/255.255.0.0 -j ACCEPT
# Allow Ganglia Access
-A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
# Gmetad access to gmond
- -A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
+ -A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
# Gmond UDP multicast
-A INPUT -m state --state NEW -m udp -p udp --dport 8649 -j ACCEPT
@@ -205,8 +206,8 @@ iptables
::
:MYSQL - [0:0]
- -A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j MYSQL
- -A MYSQL -s 192.168.0.0/255.0.0.0 -j ACCEPT
+ -A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j MYSQL
+ -A MYSQL -s 192.168.0.0/255.255.0.0 -j ACCEPT
For a host that is in the mysql-server group you get an iptables file
that looks like the following::
@@ -244,20 +245,20 @@ that looks like the following::
# Allow SSH Access
:SSH - [0:0]
- -A INPUT -p tcp -m state --state NEW -m tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 22 -j SSH
- -A SSH -s 192.168.0.0/255.0.0.0 -j ACCEPT
+ -A INPUT -p tcp -m state --state NEW -m tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 22 -j SSH
+ -A SSH -s 192.168.0.0/255.255.0.0 -j ACCEPT
# Allow Ganglia Access
-A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
#Gmetad access to gmond
- -A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
+ -A INPUT -m state --state NEW -m tcp -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --src 192.168.1.1 --dport 8649 -j ACCEPT
#Gmond UDP multicast
-A INPUT -m state --state NEW -m udp -p udp --dport 8649 -j ACCEPT
# group custom FILTER rules:
:MYSQL - [0:0]
- -A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j MYSQL
- -A MYSQL -s 192.168.0.0/255.0.0.0 -j ACCEPT
+ -A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j MYSQL
+ -A MYSQL -s 192.168.0.0/255.255.0.0 -j ACCEPT
# host-specific FILTER rules:
diff --git a/doc/server/plugins/grouping/metadata.txt b/doc/server/plugins/grouping/metadata.txt
index c52ac7612..305857578 100644
--- a/doc/server/plugins/grouping/metadata.txt
+++ b/doc/server/plugins/grouping/metadata.txt
@@ -276,22 +276,37 @@ A special client metadata class is available to the
MetadataQuery
-------------
-This class provides query routines for the servers Metadata.
+This class provides query methods for the metadata of all clients
+known to the Bcfg2 server. Note that ``*by_groups()`` and
+``*by_profiles()`` behave differently; for a client to be included in
+the return value of a ``by_groups()`` method, it must be a member of
+*all* groups listed in the argument; for a client to be included in
+the return value of a ``by_profiles()`` method, it must have any group
+listed as its profile group.
+------------------------------+------------------------------------------------+-------------------+
| Method | Description | Value |
+==============================+================================================+===================+
| by_name(client) | Get ClientMetadata object for 'client' | ClientMetadata |
+------------------------------+------------------------------------------------+-------------------+
-| names_by_groups(groups) | All client names in the list of 'groups' | List |
+| by_groups(groups) | Get ClientMetadata object for clients in all | List of |
+| | listed groups | ClientMetadata |
+------------------------------+------------------------------------------------+-------------------+
-| names_by_profiles(profiles) | All client names in the list of 'profiles' | List |
+| by_profiles(client) | Get ClientMetadata objects for clients whose | List of |
+| | profile matches any listed profile group | ClientMetadata |
+------------------------------+------------------------------------------------+-------------------+
-| all_clients() | All known client hostnames | List |
+| names_by_groups(groups) | Get the names of all clients in all listed | List of strings |
+| | groups | |
+------------------------------+------------------------------------------------+-------------------+
-| all_groups() | All known group names | List |
+| names_by_profiles(profiles) | Get the names of clients whose profile matches | List of strings |
+| | any listed profile group | |
+------------------------------+------------------------------------------------+-------------------+
-| all_groups_in_category(cat) | All groups in category 'cat' | List |
+| all_clients() | All known client hostnames | List of strings |
+------------------------------+------------------------------------------------+-------------------+
-| all() | Get ClientMetadata for all clients | List |
+| all_groups() | All known group names | List of strings |
++------------------------------+------------------------------------------------+-------------------+
+| all_groups_in_category(cat) | The names of all groups in category 'cat' | List of strings |
++------------------------------+------------------------------------------------+-------------------+
+| all() | Get ClientMetadata for all clients | List of |
+| | | ClientMetadata |
+------------------------------+------------------------------------------------+-------------------+
diff --git a/doc/server/plugins/probes/group.txt b/doc/server/plugins/probes/group.txt
index dfe64cc60..03c13db42 100644
--- a/doc/server/plugins/probes/group.txt
+++ b/doc/server/plugins/probes/group.txt
@@ -52,10 +52,10 @@ Probe used to dynamically set client groups based on OS/distro.
# redhat based
if [ -x /bin/rpm ]; then
OUTPUT="${OUTPUT}\ngroup:rpm"
- OS_GROUP=`/bin/rpm -q --qf "%{NAME}" --whatprovides redhat-release | sed 's/-release.*//' | tr '[A-Z]' '[a-z]'`
+ OS_GROUP=`/bin/rpm -q --qf "%{NAME}" --whatprovides redhat-release | grep -vi 'freeing read locks for locker' | sed 's/-release.*//' | tr '[A-Z]' '[a-z]'`
REDHAT_VERSION=`/bin/rpm -q --qf "%{VERSION}" --whatprovides redhat-release`
case "$OS_GROUP" in
- "centos" | "fedora")
+ "centos" | "fedora" | "sl")
OUTPUT="${OUTPUT}\ngroup:${OS_GROUP}"
OUTPUT="${OUTPUT}\ngroup:${OS_GROUP}-${REDHAT_VERSION}"
;;
@@ -88,7 +88,7 @@ Probe used to dynamically set client groups based on OS/distro.
ARCH=`uname -m`
case "$ARCH" in
"x86_64")
- if [ "$OS_GROUP" == 'centos' -o "$OS_GROUP" == 'redhat' ]; then
+ if [ "$OS_GROUP" == 'centos' -o "$OS_GROUP" == 'sl' -o "$OS_GROUP" == 'redhat' ]; then
OUTPUT="$OUTPUT\ngroup:${ARCH}"
else
OUTPUT="$OUTPUT\ngroup:amd64"
diff --git a/doc/server/plugins/probes/index.txt b/doc/server/plugins/probes/index.txt
index f22f405c1..95aa2d0ce 100644
--- a/doc/server/plugins/probes/index.txt
+++ b/doc/server/plugins/probes/index.txt
@@ -208,7 +208,7 @@ look something like:
<FileProbes>
<FileProbe name="/etc/foo.conf"/>
<Group name="blah-servers">
- <FileProbe name="/etc/blah.conf" update="true"
+ <FileProbe name="/etc/blah.conf" update="true"/>
</Group>
<Client name="bar.example.com">
<FileProbe name="/var/lib/bar.gz" base64="true"/>
diff --git a/examples/Bundler/sgenshi-dirvish.genshi b/examples/Bundler/sgenshi-dirvish.genshi
index 19f44a0e8..b4ea08f2c 100644
--- a/examples/Bundler/sgenshi-dirvish.genshi
+++ b/examples/Bundler/sgenshi-dirvish.genshi
@@ -2,7 +2,7 @@
vim: ft=xml
-->
<Bundle name='sgenshi-dirvish' xmlns:py="http://genshi.edgewall.org/">
-<py:for each="user in metadata.Properties['dirvish.xml'].data.find('users')">
+<py:for each="user in metadata.Properties['dirvish.xml'].xdata.find('users')">
<!-- Generate configs for all users in dirvish.xml -->
<BoundPath
name='/backup/homes/${user.tag}/dirvish/default.conf'
diff --git a/examples/TemplateHelper/include.py b/examples/TemplateHelper/include.py
new file mode 100644
index 000000000..5fba75558
--- /dev/null
+++ b/examples/TemplateHelper/include.py
@@ -0,0 +1,97 @@
+"""IncludeHelper makes it easier to include group- and host-specific files in a template.
+
+Synopsis:
+
+ {% python
+ import os
+ include = metadata.TemplateHelper['include'].IncludeHelper
+ custom = include(metadata, path).files(os.path.basename(name))
+ %}\
+ {% for file in custom %}\
+
+ ########## Start ${include.specificity(file)} ##########
+ {% include ${file} %}
+ ########## End ${include.specificity(file)} ##########
+ {% end %}\
+
+This would let you include files with the same base name; e.g. in a
+template for ''foo.conf'', the include files would be called
+''foo.conf.G_<group>.genshi_include''. If a template needs to include
+different files in different places, you can do that like so:
+
+ inc = metadata.TemplateHelper['include'].IncludeHelper(metadata, path)
+ custom_bar = inc.files("bar")
+ custom_baz = inc.files("baz")
+
+This would result in two different sets of custom files being used,
+one drawn from ''bar.conf.G_<group>.genshi_include'' and the other
+from ''baz.conf.G_<group>.genshi_include''.
+
+==== Methods ====
+
+
+=== files ===
+
+Usage:
+
+
+
+"""
+
+import os
+import re
+import Bcfg2.Options
+
+__export__ = ["IncludeHelper"]
+
+class IncludeHelper (object):
+ def __init__(self, metadata, path):
+ """ Constructor.
+
+ The template path can be found in the ''path'' variable that is set for all Genshi templates."""
+ self.metadata = metadata
+ self.path = path
+
+ def _get_basedir(self):
+ setup = Bcfg2.Options.OptionParser({'repo':
+ Bcfg2.Options.SERVER_REPOSITORY})
+ setup.parse('--')
+ return os.path.join(setup['repo'], os.path.dirname(self.path))
+
+ def files(self, fname):
+ """ Return a list of files to include for this host. Files
+ are found in the template directory based on the following
+ patterns:
+
+ * ''<prefix>.H_<hostname>.genshi_include'': Host-specific files
+ * ''<prefix>.G_<group>.genshi_include'': Group-specific files
+
+ Note that there is no numeric priority on the group-specific
+ files. All matching files are returned by
+ ''IncludeHelper.files()''. """
+ files = []
+ hostfile = os.path.join(self._get_basedir(),
+ "%s.H_%s.genshi_include" %
+ (fname, self.metadata.hostname))
+ if os.path.isfile(hostfile):
+ files.append(hostfile)
+
+ for group in self.metadata.groups:
+ filename = os.path.join(self._get_basedir(),
+ "%s.G_%s.genshi_include" % (fname, group))
+ if os.path.isfile(filename):
+ files.append(filename)
+
+ return sorted(files)
+
+ @staticmethod
+ def specificity(fname):
+ """ Get a string describing the specificity of the given file """
+ match = re.search(r'(G|H)_(.*)\.genshi_include', fname)
+ if match:
+ if match.group(1) == "G":
+ stype = "group"
+ else:
+ stype = "host"
+ return "%s-specific configs for %s" % (stype, match.group(2))
+ return "Unknown specificity"
diff --git a/examples/bcfg2-lint.conf b/examples/bcfg2-lint.conf
index 9c0d2c72a..6ca3fb61f 100644
--- a/examples/bcfg2-lint.conf
+++ b/examples/bcfg2-lint.conf
@@ -22,7 +22,7 @@ cfg_keywords =
probe_comments = Maintainer,Purpose,Groups,Other Output
[Validate]
-schema=/usr/share/bcfg2/schema
+schema=/usr/share/bcfg2/schemas
[MergeFiles]
threshold=85
diff --git a/gentoo/bcfg2-1.1.2.ebuild b/gentoo/bcfg2-1.1.2.ebuild
deleted file mode 100644
index 4da67d865..000000000
--- a/gentoo/bcfg2-1.1.2.ebuild
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 1999-2011 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-# $Header: $
-
-EAPI="3"
-PYTHON_DEPEND="2:2.6"
-SUPPORT_PYTHON_ABIS="1"
-# ssl module required.
-RESTRICT_PYTHON_ABIS="2.4 2.5 3.*"
-
-inherit distutils
-
-DESCRIPTION="Bcfg2 is a configuration management tool."
-HOMEPAGE="http://bcfg2.org"
-
-# handle the "pre" case
-MY_P="${P/_/}"
-SRC_URI="ftp://ftp.mcs.anl.gov/pub/bcfg/${MY_P}.tar.gz"
-S="${WORKDIR}/${MY_P}"
-
-LICENSE="BSD"
-SLOT="0"
-KEYWORDS="~amd64 ~x86 ~amd64-linux ~x86-linux ~x64-solaris"
-IUSE="server"
-
-DEPEND="app-portage/gentoolkit
- server? (
- dev-python/lxml
- app-admin/gam-server )"
-RDEPEND="${DEPEND}"
-
-PYTHON_MODNAME="Bcfg2"
-
-distutils_src_install_post_hook() {
- if ! use server; then
- rm -f "$(distutils_get_intermediate_installation_image)${EPREFIX}/usr/sbin/bcfg2-"*
- fi
-}
-
-src_install() {
- distutils_src_install --record=PY_SERVER_LIBS --install-scripts "${EPREFIX}/usr/sbin"
-
- # Remove files only necessary for a server installation
- if ! use server; then
- rm -rf "${ED}usr/share/bcfg2"
- rm -rf "${ED}usr/share/man/man8"
- fi
-
- # Install a server init.d script
- if use server; then
- newinitd "${FILESDIR}/bcfg2-server.rc" bcfg2-server
- fi
-
- insinto /etc
- doins examples/bcfg2.conf || die "doins failed"
-}
-
-pkg_postinst () {
- distutils_pkg_postinst
-
- if use server; then
- einfo "If this is a new installation, you probably need to run:"
- einfo " bcfg2-admin init"
- fi
-}
diff --git a/gentoo/bcfg2-1.2.2.ebuild b/gentoo/bcfg2-1.2.2.ebuild
new file mode 100644
index 000000000..c054446fe
--- /dev/null
+++ b/gentoo/bcfg2-1.2.2.ebuild
@@ -0,0 +1,79 @@
+# Copyright 1999-2011 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-x86/app-admin/bcfg2/bcfg2-1.2.0.ebuild,v 1.1 2011/12/28 07:56:20 xmw Exp $
+
+EAPI="3"
+PYTHON_DEPEND="2:2.6"
+SUPPORT_PYTHON_ABIS="1"
+# ssl module required.
+RESTRICT_PYTHON_ABIS="2.4 2.5 3.*"
+
+inherit distutils
+
+DESCRIPTION="configuration management tool"
+HOMEPAGE="http://bcfg2.org"
+SRC_URI="ftp://ftp.mcs.anl.gov/pub/bcfg/${P}.tar.gz"
+
+LICENSE="BSD"
+SLOT="0"
+KEYWORDS="~amd64 ~x86 ~amd64-linux ~x86-linux ~x64-solaris"
+IUSE="doc genshi server"
+
+DEPEND="dev-python/setuptools
+ doc? ( dev-python/sphinx )"
+RDEPEND="app-portage/gentoolkit
+ genshi? ( dev-python/genshi )
+ server? (
+ virtual/fam
+ dev-python/lxml
+ dev-libs/libgamin[python] )"
+
+PYTHON_MODNAME="Bcfg2"
+
+distutils_src_install_post_hook() {
+ if ! use server; then
+ rm -f "$(distutils_get_intermediate_installation_image)${EPREFIX}/usr/sbin/bcfg2-"*
+ fi
+}
+
+src_compile() {
+ distutils_src_compile
+
+ if use doc; then
+ einfo "Building Bcfg2 documentation"
+ PYTHONPATH="build-$(PYTHON -f --ABI)" \
+ sphinx-build doc doc_output || die
+ fi
+}
+
+src_install() {
+ distutils_src_install --record=PY_SERVER_LIBS --install-scripts "${EPREFIX}/usr/sbin"
+
+ if ! use server; then
+ # Remove files only necessary for a server installation
+ rm -rf "${ED}usr/share/bcfg2" || die
+ rm -rf "${ED}usr/share/man/man8" || die
+ else
+ newinitd "${FILESDIR}/${PN}-server-1.2.0.rc" bcfg2-server
+ fi
+
+ insinto /etc
+ doins examples/bcfg2.conf
+
+ if use doc; then
+ # install the sphinx documentation
+ pushd doc_output > /dev/null
+ insinto /usr/share/doc/${PF}/html
+ doins -r [a-z]* _images _static || die "Failed to install documentation"
+ popd > /dev/null
+ fi
+}
+
+pkg_postinst () {
+ distutils_pkg_postinst
+
+ if use server; then
+ einfo "If this is a new installation, you probably need to run:"
+ einfo " bcfg2-admin init"
+ fi
+}
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index ba091d8b1..684586892 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -316,13 +316,14 @@ control the statistics collection functionality of the server.
.TP
.B database_engine
The database engine used by the statistics module. One of either
-'postgresql', 'mysql', 'sqlite3', or 'ado_mssql'.
+\[oq]postgresql\[cq], \[oq]mysql\[cq], \[oq]sqlite3\[cq], or
+\[oq]ado_mssql\[cq].
.TP
.B database_name
The name of the database to use for statistics data. If
-'database_engine' is set to 'sqlite3' this is a file path to
-sqlite file and defaults to $REPOSITORY_DIR/etc/brpt.sqlite
+\[oq]database_engine\[cq] is set to \[oq]sqlite3\[cq] this is a file
+path to sqlite file and defaults to $REPOSITORY_DIR/etc/brpt.sqlite
.TP
.B database_user
diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec
index 54cb11ca8..75c6090a0 100644
--- a/misc/bcfg2.spec
+++ b/misc/bcfg2.spec
@@ -1,45 +1,67 @@
-%define release 0.1
+%define release 0.2
%define __python python
%{!?py_ver: %define py_ver %(%{__python} -c 'import sys;print(sys.version[0:3])')}
%define pythonversion %{py_ver}
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%{!?_initrddir: %define _initrddir %{_sysconfdir}/rc.d/init.d}
-# Most rpm-based distributions include the lxml package a 'python-lxml',
-# but some distributions and some people who roll their own lxml packages
-# call it just 'lxml'. We'll try to catch both.
-%define dfl_lxml python-lxml
-%define alt_lxml lxml
-%define lxmldep %(rpm -q %{alt_lxml} 2>&1 > /dev/null && echo %{alt_lxml} || echo %{dfl_lxml})
-
Name: bcfg2
-Version: 1.2.0
+Version: 1.2.2
Release: %{release}
Summary: Configuration management system
+%if 0%{?suse_version}
+# http://en.opensuse.org/openSUSE:Package_group_guidelines
+Group: System/Management
+%else
Group: Applications/System
+%endif
License: BSD
URL: http://bcfg2.org
Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%{name}-%{version}.tar.gz
+%if 0%{?suse_version}
+# SUSEs OBS does not understand the id macro below.
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
+%else
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-
+%endif
BuildArch: noarch
BuildRequires: python-devel
-BuildRequires: %{lxmldep}
+BuildRequires: python-lxml
+%if 0%{?mandriva_version}
+# mandriva seems to behave differently than other distros and needs this explicitly.
+BuildRequires: python-setuptools
+%endif
+%if 0%{?mandriva_version} == 201100
+# mandriva 2011 has multiple providers for libsane, so (at least when building on OBS)
+# one must be chosen explicitly:
+# "have choice for libsane.so.1 needed by python-imaging: libsane1 sane-backends-iscan"
+BuildRequires: libsane1
+%endif
# %{rhel} wasn't set before rhel 6. so this checks for old RHEL
# %systems (and potentially very old Fedora systems, too)
-%if "%{_vendor}" == "redhat" && 0%{?rhel} <= 6 && 0%{?fedora} == 0
-BuildRequires: python-sphinx10
+%if "%{_vendor}" == "redhat" && 0%{?rhel} < 6 && 0%{?fedora} == 0
+BuildRequires: python-sphinx10
# the python-sphinx10 package doesn't set sys.path correctly, so we
# have to do it for them
-%define pythonpath /usr/lib/python%{py_ver}/site-packages/Sphinx-1.0.4-py%{py_ver}.egg
+%define pythonpath %(find %{python_sitelib} -name Sphinx*.egg)
%else
-BuildRequires: python-sphinx >= 0.6
+BuildRequires: python-sphinx >= 0.6
%endif
-Requires: %{lxmldep} >= 0.9
+Requires: python-nose
+Requires: python-lxml >= 0.9
+%if 0%{?rhel_version}
+# the debian init script needs redhat-lsb.
+# iff we switch to the redhat one, this might not be needed anymore.
+Requires: redhat-lsb
+%endif
+%if "%{_vendor}" != "redhat"
+# fedora and rhel (and possibly other distros) do not know this tag.
+Recommends: cron
+%endif
%description
Bcfg2 helps system administrators produce a consistent, reproducible,
@@ -67,20 +89,28 @@ systems are constantly changing; if required in your environment,
Bcfg2 can enable the construction of complex change management and
deployment strategies.
-%package -n bcfg2-server
-Version: %{version}
-Summary: Bcfg2 Server
-Group: System Tools
-Requires: bcfg2
+This package includes the Bcfg2 client software.
+
+%package server
+Version: 1.2.2
+Summary: Bcfg2 Server
+%if 0%{?suse_version}
+Group: System/Management
+%else
+Group: System Tools
+%endif
+Requires: bcfg2
%if "%{py_ver}" < "2.6"
Requires: python-ssl
%endif
-Requires: %{lxmldep} >= 1.2.1
+Requires: python-lxml >= 1.2.1
%if "%{_vendor}" == "redhat"
-Requires: gamin-python
+Requires: gamin-python
%endif
+Requires: /usr/sbin/sendmail
+Requires: /usr/bin/openssl
-%description -n bcfg2-server
+%description server
Bcfg2 helps system administrators produce a consistent, reproducible,
and verifiable description of their environment, and offers
visualization and reporting tools to aid in day-to-day administrative
@@ -106,28 +136,63 @@ systems are constantly changing; if required in your environment,
Bcfg2 can enable the construction of complex change management and
deployment strategies.
-%package -n bcfg2-doc
+This package includes the Bcfg2 server software.
+
+%package doc
Summary: Configuration management system documentation
+%if 0%{?suse_version}
+Group: Documentation/HTML
+%else
Group: Documentation
+%endif
+
+%description doc
+Bcfg2 helps system administrators produce a consistent, reproducible,
+and verifiable description of their environment, and offers
+visualization and reporting tools to aid in day-to-day administrative
+tasks. It is the fifth generation of configuration management tools
+developed in the Mathematics and Computer Science Division of Argonne
+National Laboratory.
-%description -n bcfg2-doc
-Configuration management system documentation
+It is based on an operational model in which the specification can be
+used to validate and optionally change the state of clients, but in a
+feature unique to bcfg2 the client's response to the specification can
+also be used to assess the completeness of the specification. Using
+this feature, bcfg2 provides an objective measure of how good a job an
+administrator has done in specifying the configuration of client
+systems. Bcfg2 is therefore built to help administrators construct an
+accurate, comprehensive specification.
-%package -n bcfg2-web
-Version: %{version}
-Summary: Bcfg2 Web Reporting Interface
-Group: System Tools
-Requires: bcfg2-server
-Requires: httpd,Django
+Bcfg2 has been designed from the ground up to support gentle
+reconciliation between the specification and current client states. It
+is designed to gracefully cope with manual system modifications.
+
+Finally, due to the rapid pace of updates on modern networks, client
+systems are constantly changing; if required in your environment,
+Bcfg2 can enable the construction of complex change management and
+deployment strategies.
+
+This package includes the Bcfg2 documentation.
+
+%package web
+Version: 1.2.2
+Summary: Bcfg2 Web Reporting Interface
+%if 0%{?suse_version}
+Group: System/Management
+%else
+Group: System Tools
+%endif
+Requires: bcfg2-server
+Requires: httpd,Django
%if "%{_vendor}" == "redhat"
-Requires: mod_wsgi
+Requires: mod_wsgi
%define apache_conf %{_sysconfdir}/httpd
%else
-Requires: apache2-mod_wsgi
+Requires: apache2-mod_wsgi
%define apache_conf %{_sysconfdir}/apache2
%endif
-%description -n bcfg2-web
+%description web
Bcfg2 helps system administrators produce a consistent, reproducible,
and verifiable description of their environment, and offers
visualization and reporting tools to aid in day-to-day administrative
@@ -153,8 +218,10 @@ systems are constantly changing; if required in your environment,
Bcfg2 can enable the construction of complex change management and
deployment strategies.
+This package includes the Bcfg2 reports web frontend.
+
%prep
-%setup -q -n bcfg2-%{version}
+%setup -q -n %{name}-%{version}
%build
%{__python}%{pythonversion} setup.py build
@@ -164,6 +231,7 @@ deployment strategies.
%{__python}%{pythonversion} setup.py build_sphinx
%install
+rm -rf %{buildroot}
%{__python}%{pythonversion} setup.py install --root=%{buildroot} --record=INSTALLED_FILES --prefix=/usr
%{__install} -d %{buildroot}%{_bindir}
%{__install} -d %{buildroot}%{_sbindir}
@@ -173,8 +241,11 @@ deployment strategies.
%{__install} -d %{buildroot}%{_sysconfdir}/cron.hourly
%{__install} -d %{buildroot}%{_prefix}/lib/bcfg2
mkdir -p %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}
+%if 0%{?suse_version}
+%{__install} -d %{buildroot}/var/adm/fillup-templates
+%endif
-%{__mv} %{buildroot}/usr/bin/bcfg2* %{buildroot}%{_sbindir}
+%{__mv} %{buildroot}%{_bindir}/bcfg2* %{buildroot}%{_sbindir}
%{__install} -m 755 debian/bcfg2.init %{buildroot}%{_initrddir}/bcfg2
%{__install} -m 755 debian/bcfg2-server.init %{buildroot}%{_initrddir}/bcfg2-server
%{__install} -m 755 debian/bcfg2.default %{buildroot}%{_sysconfdir}/default/bcfg2
@@ -182,6 +253,12 @@ mkdir -p %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}
%{__install} -m 755 debian/bcfg2.cron.daily %{buildroot}%{_sysconfdir}/cron.daily/bcfg2
%{__install} -m 755 debian/bcfg2.cron.hourly %{buildroot}%{_sysconfdir}/cron.hourly/bcfg2
%{__install} -m 755 tools/bcfg2-cron %{buildroot}%{_prefix}/lib/bcfg2/bcfg2-cron
+%if 0%{?suse_version}
+%{__install} -m 755 debian/bcfg2.default %{buildroot}/var/adm/fillup-templates/sysconfig.bcfg2
+%{__install} -m 755 debian/bcfg2-server.default %{buildroot}/var/adm/fillup-templates/sysconfig.bcfg2-server
+ln -s %{_initrddir}/bcfg2 %{buildroot}%{_sbindir}/rcbcfg2
+ln -s %{_initrddir}/bcfg2-server %{buildroot}%{_sbindir}/rcbcfg2-server
+%endif
mv build/sphinx/html/* %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}
mv build/dtd %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}/
@@ -191,13 +268,19 @@ mv build/dtd %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}/
%{__mkdir_p} %{buildroot}%{_localstatedir}/cache/bcfg2
+# mandriva and RHEL 5 cannot handle %ghost without the file existing,
+# so let's touch a bunch of empty config files
+touch %{buildroot}%{_sysconfdir}/bcfg2.conf %{buildroot}%{_sysconfdir}/bcfg2-web.conf
+
%clean
[ "%{buildroot}" != "/" ] && %{__rm} -rf %{buildroot} || exit 2
-%files -n bcfg2
+%files
%defattr(-,root,root,-)
%{_sbindir}/bcfg2
+%dir %{python_sitelib}/Bcfg2
%{python_sitelib}/Bcfg2/*.py*
+%dir %{python_sitelib}/Bcfg2/Client
%{python_sitelib}/Bcfg2/Client/*
%{_mandir}/man1/bcfg2.1*
%{_mandir}/man5/bcfg2.conf.5*
@@ -207,15 +290,16 @@ mv build/dtd %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}/
%{_sysconfdir}/cron.daily/bcfg2
%{_prefix}/lib/bcfg2/bcfg2-cron
%{_localstatedir}/cache/bcfg2
+%if 0%{?suse_version}
+%{_sbindir}/rcbcfg2
+%config(noreplace) /var/adm/fillup-templates/sysconfig.bcfg2
+%endif
+%ghost %config(noreplace,missingok) %attr(0600,root,root) %{_sysconfdir}/bcfg2.conf
-%post -n bcfg2-server
-/sbin/chkconfig --add bcfg2-server
-
-%files -n bcfg2-server
+%files server
%defattr(-,root,root,-)
-
%{_initrddir}/bcfg2-server
-
+%dir %{python_sitelib}/Bcfg2
%{python_sitelib}/Bcfg2/Server
%if "%{pythonversion}" >= "2.5"
@@ -236,30 +320,84 @@ mv build/dtd %{buildroot}%{_defaultdocdir}/bcfg2-doc-%{version}/
%{_sbindir}/bcfg2-reports
%{_sbindir}/bcfg2-server
%{_sbindir}/bcfg2-yum-helper
+%{_sbindir}/bcfg2-test
+%if 0%{?suse_version}
+%{_sbindir}/rcbcfg2-server
+%config(noreplace) /var/adm/fillup-templates/sysconfig.bcfg2-server
+%endif
%{_mandir}/man5/bcfg2-lint.conf.5*
%{_mandir}/man8/*.8*
%dir %{_prefix}/lib/bcfg2
+%ghost %config(noreplace,missingok) %attr(0600,root,root) %{_sysconfdir}/bcfg2.conf
%files doc
%defattr(-,root,root,-)
%doc %{_defaultdocdir}/bcfg2-doc-%{version}
-%files -n bcfg2-web
+%files web
%defattr(-,root,root,-)
-
%{_datadir}/bcfg2/reports.wsgi
%{_datadir}/bcfg2/site_media
-
+%dir %{apache_conf}
+%dir %{apache_conf}/conf.d
%config(noreplace) %{apache_conf}/conf.d/wsgi_bcfg2.conf
+%ghost %config(noreplace,missingok) %attr(0640,root,apache) %{_sysconfdir}/bcfg2-web.conf
+
+%post server
+# enable daemon on first install only (not on update).
+if [ $1 -eq 1 ]; then
+%if 0%{?suse_version}
+ %fillup_and_insserv -f bcfg2-server
+%else
+ /sbin/chkconfig --add bcfg2-server
+%endif
+fi
+
+%preun
+%if 0%{?suse_version}
+# stop on removal (not on update).
+if [ $1 -eq 0 ]; then
+ %stop_on_removal bcfg2
+fi
+%endif
+
+%preun server
+%if 0%{?suse_version}
+if [ $1 -eq 0 ]; then
+ %stop_on_removal bcfg2-server
+fi
+%endif
+
+%postun
+%if 0%{?suse_version}
+if [ $1 -eq 0 ]; then
+ %insserv_cleanup
+fi
+%endif
+
+%postun server
+%if 0%{?suse_version}
+if [ $1 -eq 0 ]; then
+ # clean up on removal.
+ %insserv_cleanup
+fi
+%endif
%changelog
-%changelog
+* Sat Feb 18 2012 Christopher 'm4z' Holm <686f6c6d@googlemail.com> 1.2.1
+- Added Fedora and Mandriva compatibilty (for Open Build Service).
+- Added missing dependency redhat-lsb.
+
+* Tue Feb 14 2012 Christopher 'm4z' Holm <686f6c6d@googlemail.com> 1.2.1
+- Added openSUSE compatibility.
+- Various changes to satisfy rpmlint.
+
* Thu Jan 27 2011 Chris St. Pierre <stpierreca@ornl.gov> 1.2.0pre1-0.0
- Added -doc sub-package
* Mon Jun 21 2010 Fabian Affolter <fabian@bernewireless.net> - 1.1.0rc3-0.1
-- Changed source0 in order that it works with spectool
+- Changed source0 in order that it works with spectool
* Fri Feb 2 2007 Mike Brady <mike.brady@devnull.net.nz> 0.9.1
- Removed use of _libdir due to Red Hat x86_64 issue.
diff --git a/osx/Makefile b/osx/Makefile
index aa0c13650..72751ff32 100644
--- a/osx/Makefile
+++ b/osx/Makefile
@@ -29,9 +29,9 @@ SITELIBDIR = /Library/Python/${PYVERSION}/site-packages
# an Info.plist file for packagemaker to look at for package creation
# and substitute the version strings. Major/Minor versions can only be
# integers (e.g. "1" and "00" for bcfg2 version 1.0.0.
-BCFGVER = 1.2.0
+BCFGVER = 1.2.2
MAJOR = 1
-MINOR = 20
+MINOR = 22
default: clean client
diff --git a/osx/macports/Portfile b/osx/macports/Portfile
index ed9d9b25b..59de46f8c 100644
--- a/osx/macports/Portfile
+++ b/osx/macports/Portfile
@@ -5,7 +5,7 @@ PortSystem 1.0
PortGroup python26 1.0
name bcfg2
-version 1.1.1
+version 1.2.0
categories sysutils python
maintainers gmail.com:sol.jerome
license BSD
@@ -20,8 +20,8 @@ homepage http://www.bcfg2.org/
platforms darwin
master_sites ftp://ftp.mcs.anl.gov/pub/bcfg
-checksums sha1 c0214d28796805ff8e3522d348914f366ba860aa \
- rmd160 2b7d8dfc2e14d1a2def743fe525ee91c5a3d3342
+checksums rmd160 db89ee0b8975bf50ad68bfac122e50253ded1906 \
+ sha256 138d792423475ae6516a95578a3df191504afaff31007877c7c2b36830d1a260
patchfiles patch-setup.py.diff
diff --git a/redhat/Makefile b/redhat/Makefile
index 7533b98da..f8f779557 100644
--- a/redhat/Makefile
+++ b/redhat/Makefile
@@ -22,7 +22,7 @@ VERSION := $(shell cat VERSION)
RELEASE := $(shell cat RELEASE)
BASE_VER := ${VERSION}-${RELEASE}
CURRENT_PACKAGE := $(PACKAGE)-$(BASE_VER)
-TARBALL := $(CURRENT_PACKAGE).tar
+TARBALL := $(PACKAGE)-$(VERSION).tar
DIRNAME := $(shell echo $${PWD})
DIRBASE := $(shell basename $${PWD})
diff --git a/redhat/VERSION b/redhat/VERSION
index 26aaba0e8..23aa83906 100644
--- a/redhat/VERSION
+++ b/redhat/VERSION
@@ -1 +1 @@
-1.2.0
+1.2.2
diff --git a/redhat/bcfg2.spec.in b/redhat/bcfg2.spec.in
index be2375ced..64adbe5c2 100644
--- a/redhat/bcfg2.spec.in
+++ b/redhat/bcfg2.spec.in
@@ -44,6 +44,7 @@ Requires: python-elementtree
%else if "%{py_ver}" < "2.5"
Requires: python-lxml
%endif
+Requires: python-nose
Requires: initscripts
Requires(post): /sbin/chkconfig
Requires(preun): /sbin/chkconfig
@@ -117,7 +118,7 @@ Configuration management system documentation
%{__perl} -pi -e 's@chkconfig: (\d+)@chkconfig: -@' debian/bcfg2-server.init
# get rid of extraneous shebangs
-for f in `find src/lib -name \*.py`
+for f in `find src/lib/Bcfg2 -name \*.py`
do
%{__sed} -i -e '/^#!/,1d' $f
done
@@ -204,7 +205,7 @@ fi
%{python_sitelib}/Bcfg2*.egg-info
%dir %{python_sitelib}/Bcfg2
-%{python_sitelib}/Bcfg2/__init__.*
+%{python_sitelib}/Bcfg2/*.py*
%{python_sitelib}/Bcfg2/Client
%{python_sitelib}/Bcfg2/Component.*
%{python_sitelib}/Bcfg2/Logger.*
@@ -241,6 +242,7 @@ fi
%{_sbindir}/bcfg2-reports
%{_sbindir}/bcfg2-server
%{_sbindir}/bcfg2-yum-helper
+%{_sbindir}/bcfg2-test
%{_mandir}/man5/bcfg2-lint.conf.5*
%{_mandir}/man8/*.8*
diff --git a/reports/xsl-transforms/xsl-transform-includes/html-templates.xsl b/reports/xsl-transforms/xsl-transform-includes/html-templates.xsl
index 74adbfe94..ad29f0411 100644
--- a/reports/xsl-transforms/xsl-transform-includes/html-templates.xsl
+++ b/reports/xsl-transforms/xsl-transform-includes/html-templates.xsl
@@ -4,7 +4,7 @@
<xsl:if test="count(Statistics/Good)+count(Statistics/Bad)+count(Statistics/Extra)+count(Statistics/Modified)+count(Statistics/Stale) > 0">
<a name="{Client/@name}}"></a>
- <div class="nodebox"">
+ <div class="nodebox">
<span class="notebox">Time Ran: <xsl:value-of select="Statistics/@time" /></span>
<span class="configbox">(<xsl:value-of select="Client/@profile" />)</span>
diff --git a/schemas/base.xsd b/schemas/base.xsd
index 91b7ac8f5..cca665b38 100644
--- a/schemas/base.xsd
+++ b/schemas/base.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
base schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/bundle.xsd b/schemas/bundle.xsd
index 6b32434be..4e034ee3c 100644
--- a/schemas/bundle.xsd
+++ b/schemas/bundle.xsd
@@ -1,6 +1,6 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:py="http://genshi.edgewall.org/" xml:lang="en">
-
+
<xsd:annotation>
<xsd:documentation>
bundle schema for bcfg2
@@ -119,13 +119,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute type='xsd:string' name='name' use='required'>
<xsd:annotation>
@@ -235,13 +229,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute type='xsd:string' name='description' />
<xsd:attribute type='xsd:string' name='name'/>
@@ -249,6 +237,7 @@
<xsd:attribute type='xsd:string' name='origin'/>
<xsd:attribute type='xsd:string' name='revision'/>
<xsd:attributeGroup ref="py:genshiAttrs"/>
+ <xsd:attribute ref="xml:base"/>
</xsd:complexType>
<xsd:element name='Bundle' type='BundleType'>
diff --git a/schemas/clients.xsd b/schemas/clients.xsd
index 0a9ce5202..56f458a45 100644
--- a/schemas/clients.xsd
+++ b/schemas/clients.xsd
@@ -1,19 +1,21 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en">
-
+
<xsd:annotation>
<xsd:documentation>
client schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="xml.xsd"/>
+
<xsd:complexType name='ClientType'>
<xsd:choice minOccurs='0' maxOccurs='unbounded'>
<xsd:element name='Alias'>
<xsd:complexType>
<xsd:attribute type='xsd:string' name='name' use='required'/>
- <xsd:attribute type='xsd:string' name='address'/>
+ <xsd:attribute type='xsd:string' name='address'/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -35,6 +37,7 @@
<xsd:element name='Clients' type='ClientsType'/>
</xsd:choice>
<xsd:attribute name='version' type='xsd:string'/>
+ <xsd:attribute ref="xml:base"/>
</xsd:complexType>
<xsd:element name='Clients' type='ClientsType'/>
diff --git a/schemas/decisions.xsd b/schemas/decisions.xsd
index a354ec8cb..30115b367 100644
--- a/schemas/decisions.xsd
+++ b/schemas/decisions.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
decision list schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/defaults.xsd b/schemas/defaults.xsd
index 27e749470..c7e2edc7e 100644
--- a/schemas/defaults.xsd
+++ b/schemas/defaults.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
string enumeration definitions for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/genshi.xsd b/schemas/genshi.xsd
index 4d3ad0e31..35d81e2f1 100644
--- a/schemas/genshi.xsd
+++ b/schemas/genshi.xsd
@@ -8,12 +8,10 @@
<xs:documentation>
Genshi schema
Chris St. Pierre
- $Id$
</xs:documentation>
</xs:annotation>
- <!-- genshi tags -->
- <xs:element name="for" type="py:forType"/>
+ <!-- genshi types -->
<xs:complexType name="forType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -21,7 +19,6 @@
<xs:attribute name="each" type="xs:string" use="required"/>
</xs:complexType>
- <xs:element name="if" type="py:ifType"/>
<xs:complexType name="ifType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -29,7 +26,6 @@
<xs:attribute name="test" type="xs:string" use="required"/>
</xs:complexType>
- <xs:element name="match" type="py:matchType"/>
<xs:complexType name="matchType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -40,7 +36,6 @@
<xs:attribute name="recursive" type="xs:boolean" default="true"/>
</xs:complexType>
- <xs:element name="def" type="py:defType"/>
<xs:complexType name="defType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -48,7 +43,6 @@
<xs:attribute name="function" type="xs:string" use="required"/>
</xs:complexType>
- <xs:element name="with" type="py:withType"/>
<xs:complexType name="withType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -56,7 +50,6 @@
<xs:attribute name="vars" type="xs:string" use="required"/>
</xs:complexType>
- <xs:element name="replace" type="py:replaceType"/>
<xs:complexType name="replaceType" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:any processContents="lax"/>
@@ -64,7 +57,6 @@
<xs:attribute name="value" type="xs:string" use="required"/>
</xs:complexType>
- <xs:element name="choose" type="py:chooseType"/>
<xs:complexType name="chooseType" mixed="true">
<xs:sequence>
<xs:element name="when" type="py:ifType" maxOccurs="unbounded"/>
@@ -80,6 +72,19 @@
</xs:choice>
</xs:complexType>
+ <!-- genshi tags -->
+ <xs:group name="genshiElements">
+ <xs:choice>
+ <xs:element name="with" type="py:withType"/>
+ <xs:element name="replace" type="py:replaceType"/>
+ <xs:element name="choose" type="py:chooseType"/>
+ <xs:element name="for" type="py:forType"/>
+ <xs:element name="if" type="py:ifType"/>
+ <xs:element name="match" type="py:matchType"/>
+ <xs:element name="def" type="py:defType"/>
+ </xs:choice>
+ </xs:group>
+
<!-- genshi attributes -->
<xs:attributeGroup name="genshiAttrs">
<xs:attribute name="if" type="xs:string" form="qualified"/>
diff --git a/schemas/grouppatterns.xsd b/schemas/grouppatterns.xsd
index f2bdceccd..6c63b8694 100644
--- a/schemas/grouppatterns.xsd
+++ b/schemas/grouppatterns.xsd
@@ -1,5 +1,5 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en">
-
+
<xsd:annotation>
<xsd:documentation>
group patterns config schema for bcfg2
@@ -7,6 +7,9 @@
</xsd:documentation>
</xsd:annotation>
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="xml.xsd"/>
+
<xsd:complexType name="PatternType">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="NameRange" type="xsd:string"/>
@@ -21,6 +24,7 @@
<xsd:element name="GroupPattern" type="PatternType"/>
<xsd:element name="GroupPatterns" type="GroupPatternsType"/>
</xsd:choice>
+ <xsd:attribute ref="xml:base"/>
</xsd:complexType>
<xsd:element name="GroupPatterns" type="GroupPatternsType"/>
diff --git a/schemas/metadata.xsd b/schemas/metadata.xsd
index 58f9e8029..f79039d25 100644
--- a/schemas/metadata.xsd
+++ b/schemas/metadata.xsd
@@ -5,7 +5,6 @@
<xsd:documentation>
metadata schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/packages.xsd b/schemas/packages.xsd
index 9f16a23c0..c29a85ecf 100644
--- a/schemas/packages.xsd
+++ b/schemas/packages.xsd
@@ -1,5 +1,5 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en">
-
+
<xsd:annotation>
<xsd:documentation>
packages config schema for bcfg2
@@ -7,30 +7,27 @@
</xsd:documentation>
</xsd:annotation>
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="xml.xsd"/>
+
<xsd:simpleType name="sourceTypeEnum">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="yum"/>
<xsd:enumeration value="apt"/>
<xsd:enumeration value="pac"/>
- <xsd:enumeration value="pulp"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="sourceType">
- <xsd:sequence minOccurs="0" maxOccurs="unbounded">
- <xsd:element name="Component" type="xsd:string" minOccurs="0"
- maxOccurs="unbounded"/>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="Component" type="xsd:string"/>
+ <xsd:element name="Arch" type="xsd:string"/>
+ <xsd:element name="GPGKey" type="xsd:string"/>
<xsd:choice>
- <xsd:element name="Blacklist" type="xsd:string" minOccurs="0"
- maxOccurs="unbounded"/>
- <xsd:element name="Whitelist" type="xsd:string" minOccurs="0"
- maxOccurs="unbounded"/>
+ <xsd:element name="Blacklist" type="xsd:string"/>
+ <xsd:element name="Whitelist" type="xsd:string"/>
</xsd:choice>
- <xsd:element name="Arch" type="xsd:string" minOccurs="1"
- maxOccurs="unbounded"/>
- <xsd:element name="GPGKey" type="xsd:string" minOccurs="0"
- maxOccurs="unbounded"/>
- </xsd:sequence>
+ </xsd:choice>
<xsd:attribute type="xsd:boolean" name="recommended"/>
<xsd:attribute type="sourceTypeEnum" name="type"/>
<xsd:attribute type="xsd:string" name="pulp_id"/>
@@ -51,14 +48,13 @@
</xsd:complexType>
<xsd:complexType name="sourcesType">
- <xsd:sequence>
- <xsd:choice minOccurs="1" maxOccurs="unbounded">
- <xsd:element name="Group" type="groupType"/>
- <xsd:element name="Client" type="groupType"/>
- <xsd:element name="Source" type="sourceType"/>
- <xsd:element name="Sources" type="sourcesType"/>
- </xsd:choice>
- </xsd:sequence>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="Group" type="groupType"/>
+ <xsd:element name="Client" type="groupType"/>
+ <xsd:element name="Source" type="sourceType"/>
+ <xsd:element name="Sources" type="sourcesType"/>
+ </xsd:choice>
+ <xsd:attribute ref="xml:base"/>
</xsd:complexType>
<xsd:element name="Sources" type="sourcesType"/>
diff --git a/schemas/pathentry.xsd b/schemas/pathentry.xsd
index 40aa4ff2b..080758d0b 100644
--- a/schemas/pathentry.xsd
+++ b/schemas/pathentry.xsd
@@ -5,7 +5,6 @@
<xsd:documentation>
path entry schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
@@ -30,6 +29,12 @@
<xsd:attribute type='xsd:string' name='sensitive' use='optional'/>
<xsd:attribute type='xsd:string' name='to' use='optional'/>
<xsd:attribute type='xsd:string' name='type' use='optional'/>
+ <!-- device attributes -->
+ <xsd:attribute type='xsd:string' name='dev_type' use='optional'/>
+ <xsd:attribute type='xsd:string' name='major' use='optional'/>
+ <xsd:attribute type='xsd:string' name='minor' use='optional'/>
+ <xsd:attribute type='xsd:string' name='mode' use='optional'/>
+ <!-- end device attributes -->
<xsd:attributeGroup ref="py:genshiAttrs"/>
</xsd:complexType>
</xsd:schema>
diff --git a/schemas/pkglist.xsd b/schemas/pkglist.xsd
index c16ed654e..c0d449f54 100644
--- a/schemas/pkglist.xsd
+++ b/schemas/pkglist.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
package list schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/pkgtype.xsd b/schemas/pkgtype.xsd
index 83e3f0e48..0aaea0c22 100644
--- a/schemas/pkgtype.xsd
+++ b/schemas/pkgtype.xsd
@@ -5,7 +5,6 @@
<xsd:documentation>
package list schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
@@ -39,13 +38,7 @@
<xsd:attributeGroup ref="py:genshiAttrs"/>
</xsd:complexType>
</xsd:element>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute type="xsd:string" name="name"/>
<xsd:attribute type="xsd:string" name="group"/>
@@ -57,7 +50,8 @@
<xsd:attribute type="xsd:string" name="srcs"/>
<xsd:attribute type="PackageTypeEnum" name="type"/>
<xsd:attribute type="xsd:string" name="bname"/>
- <xsd:attribute name="pkg_checks" type="xsd:string"/>
+ <xsd:attribute name="pkg_checks" type="xsd:boolean"/>
+ <xsd:attribute name="pkg_verify" type="xsd:boolean"/>
<xsd:attribute name="verify_flags" type="xsd:string"/>
<xsd:attributeGroup ref="py:genshiAttrs"/>
</xsd:complexType>
diff --git a/schemas/rules.xsd b/schemas/rules.xsd
index 193d63c99..924792b18 100644
--- a/schemas/rules.xsd
+++ b/schemas/rules.xsd
@@ -5,7 +5,6 @@
<xsd:documentation>
string enumeration definitions for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
@@ -56,13 +55,7 @@
<xsd:element name='Action' type='ActionType'/>
<xsd:element name='Group' type='RContainerType'/>
<xsd:element name='Client' type='RContainerType'/>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute name='name' type='xsd:string'/>
<xsd:attribute name='negate' type='xsd:boolean'/>
@@ -80,13 +73,7 @@
<xsd:element name='PostInstall' type='PostInstallType'/>
<xsd:element name='Group' type='RContainerType'/>
<xsd:element name='Client' type='RContainerType'/>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute name='priority' type='xsd:integer' use='required'/>
<xsd:attributeGroup ref="py:genshiAttrs"/>
diff --git a/schemas/services.xsd b/schemas/services.xsd
index 828959e82..b91e851d2 100644
--- a/schemas/services.xsd
+++ b/schemas/services.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
services schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/servicetype.xsd b/schemas/servicetype.xsd
index f88260c39..af5bc64a6 100644
--- a/schemas/servicetype.xsd
+++ b/schemas/servicetype.xsd
@@ -5,7 +5,6 @@
<xsd:documentation>
services schema for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
@@ -21,13 +20,7 @@
<xsd:attribute name="mask" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
- <xsd:element ref="py:def"/>
- <xsd:element ref="py:match"/>
- <xsd:element ref="py:choose"/>
- <xsd:element ref="py:for"/>
- <xsd:element ref="py:if"/>
- <xsd:element ref="py:with"/>
- <xsd:element ref="py:replace"/>
+ <xsd:group ref="py:genshiElements"/>
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="status" type="StatusEnum"/>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index 689e693b7..ead377192 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -4,7 +4,6 @@
<xsd:documentation>
string enumeration definitions for bcfg2
Narayan Desai, Argonne National Laboratory
- $Id$
</xsd:documentation>
</xsd:annotation>
diff --git a/setup.py b/setup.py
index 0f51ed4c5..64dcdeb50 100755
--- a/setup.py
+++ b/setup.py
@@ -8,6 +8,13 @@ import os
import os.path
import sys
+# we only need m2crypto on < python2.6
+need_m2crypto = False
+version = sys.version_info[:2]
+if version < (2, 6):
+ need_m2crypto = True
+
+
class BuildDTDDoc (Command):
"""Build DTD documentation"""
@@ -29,8 +36,8 @@ class BuildDTDDoc (Command):
self.build_links = False
self.links_file = None
self.source_dir = None
- self.build_dir = None
- self.xslt = None
+ self.build_dir = None
+ self.xslt = None
def finalize_options(self):
"""Set final values for all the options that this command
@@ -65,7 +72,7 @@ class BuildDTDDoc (Command):
self.announce("Using XSLT file %s" % self.xslt)
self.ensure_filename('xslt')
- def run (self):
+ def run(self):
"""Perform XSLT transforms, writing output to self.build_dir"""
xslt = lxml.etree.parse(self.xslt).getroot()
@@ -75,18 +82,18 @@ class BuildDTDDoc (Command):
self.announce("Building linksFile %s" % self.links_file)
links_xml = \
lxml.etree.Element('links',
- attrib={'xmlns':"http://titanium.dstc.edu.au/xml/xs3p"})
+ attrib={'xmlns': "http://titanium.dstc.edu.au/xml/xs3p"})
for filename in glob(os.path.join(self.source_dir, '*.xsd')):
- attrib = {'file-location':os.path.basename(filename),
- 'docfile-location':os.path.splitext(os.path.basename(filename))[0] + ".html"}
+ attrib = {'file-location': os.path.basename(filename),
+ 'docfile-location': os.path.splitext(os.path.basename(filename))[0] + ".html"}
links_xml.append(lxml.etree.Element('schema', attrib=attrib))
open(os.path.join(self.source_dir, self.links_file),
"w").write(lxml.etree.tostring(links_xml))
# build parameter dict
- params = {'printLegend':"'false'",
- 'printGlossary':"'false'",
- 'sortByComponent':"'false'",}
+ params = {'printLegend': "'false'",
+ 'printGlossary': "'false'",
+ 'sortByComponent': "'false'"}
if self.links_file is not None:
params['linksFile'] = "'%s'" % self.links_file
params['searchIncludedSchemas'] = "'true'"
@@ -104,24 +111,18 @@ class BuildDTDDoc (Command):
cmdclass = {}
try:
- from sphinx.setup_command import BuildDoc
- cmdclass['build_sphinx'] = BuildDoc
-except ImportError:
- pass
-
-try:
import lxml.etree
cmdclass['build_dtddoc'] = BuildDTDDoc
except ImportError:
pass
-py3lib = 'src/lib/Bcfg2Py3Incompat.py'
-if sys.hexversion < 0x03000000 and os.path.exists(py3lib):
- os.remove(py3lib)
+inst_reqs = ["lxml", "nose"]
+if need_m2crypto:
+ inst_reqs.append("M2Crypto")
setup(cmdclass=cmdclass,
name="Bcfg2",
- version="1.2.1a1",
+ version="1.2.2",
description="Bcfg2 Server",
author="Narayan Desai",
author_email="desai@mcs.anl.gov",
@@ -140,31 +141,30 @@ setup(cmdclass=cmdclass,
"Bcfg2.Server.Reports.reports.templatetags",
"Bcfg2.Server.Snapshots",
],
- install_requires = ["lxml",
- "M2Crypto",
- ],
- package_dir = {'Bcfg2': 'src/lib'},
- package_data = {'Bcfg2.Server.Reports.reports':['fixtures/*.xml',
- 'templates/*.html', 'templates/*/*.html',
- 'templates/*/*.inc' ] },
- scripts = glob('src/sbin/*'),
- data_files = [('share/bcfg2/schemas',
- glob('schemas/*.xsd')),
- ('share/bcfg2/xsl-transforms',
- glob('reports/xsl-transforms/*.xsl')),
- ('share/bcfg2/xsl-transforms/xsl-transform-includes',
- glob('reports/xsl-transforms/xsl-transform-includes/*.xsl')),
- ('share/bcfg2', glob('reports/reports.wsgi')),
- ('share/man/man1', glob("man/bcfg2.1")),
- ('share/man/man5', glob("man/*.5")),
- ('share/man/man8', glob("man/*.8")),
- ('share/bcfg2/Hostbase/templates',
- glob('src/lib/Server/Hostbase/hostbase/webtemplates/*.*')),
- ('share/bcfg2/Hostbase/templates/hostbase',
- glob('src/lib/Server/Hostbase/hostbase/webtemplates/hostbase/*')),
- ('share/bcfg2/Hostbase/repo',
- glob('src/lib/Server/Hostbase/templates/*')),
- ('share/bcfg2/site_media',
- glob('reports/site_media/*')),
- ]
+ install_requires=inst_reqs,
+ package_dir={'Bcfg2': 'src/lib/Bcfg2'},
+ package_data={'Bcfg2.Server.Reports.reports': ['fixtures/*.xml',
+ 'templates/*.html',
+ 'templates/*/*.html',
+ 'templates/*/*.inc']},
+ scripts=glob('src/sbin/*'),
+ data_files=[('share/bcfg2/schemas',
+ glob('schemas/*.xsd')),
+ ('share/bcfg2/xsl-transforms',
+ glob('reports/xsl-transforms/*.xsl')),
+ ('share/bcfg2/xsl-transforms/xsl-transform-includes',
+ glob('reports/xsl-transforms/xsl-transform-includes/*.xsl')),
+ ('share/bcfg2', glob('reports/reports.wsgi')),
+ ('share/man/man1', glob("man/bcfg2.1")),
+ ('share/man/man5', glob("man/*.5")),
+ ('share/man/man8', glob("man/*.8")),
+ ('share/bcfg2/Hostbase/templates',
+ glob('src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/*.*')),
+ ('share/bcfg2/Hostbase/templates/hostbase',
+ glob('src/lib/Bcfg2/Server/Hostbase/hostbase/webtemplates/hostbase/*')),
+ ('share/bcfg2/Hostbase/repo',
+ glob('src/lib/Bcfg2/Server/Hostbase/templates/*')),
+ ('share/bcfg2/site_media',
+ glob('reports/site_media/*')),
+ ]
)
diff --git a/solaris/Makefile b/solaris/Makefile
index 816f93e14..77d9019eb 100644
--- a/solaris/Makefile
+++ b/solaris/Makefile
@@ -1,26 +1,28 @@
#!/usr/sfw/bin/gmake
-PYTHON="/opt/csw/bin/python"
-VERS=1.2.0-1
+PYTHON="/usr/local/bin/python"
+VERS=1.2.2-1
PYVERSION := $(shell $(PYTHON) -c "import sys; print sys.version[0:3]")
default: clean package
package:
- -mkdir tmp tmp/bcfg2-server tmp/bcfg2
- -cd ../ && $(PYTHON) setup.py install --prefix=$(PWD)
+ -mkdir tmp tmp/bcfg2-server tmp/bcfg2
+ -mkdir -p build/lib/$(PYVERSION)/site-packages
+ -cd ../ && PYTHONPATH=$(PYTHONPATH):$(PWD)/build/lib/python2.6/site-packages/ $(PYTHON) setup.py install --single-version-externally-managed --record=/dev/null --prefix=$(PWD)/build
+ #setuptools appears to use a restictive umask
+ -chmod -R o+r build/
-cat bin/bcfg2 | sed -e 's!/usr/bin/python!$(PYTHON)!' > bin/bcfg2.new && mv bin/bcfg2.new bin/bcfg2
- # Set python version to whichever version is installed
- -cat prototype.bcfg2 | sed -e 's!PYVERSION!python$(PYVERSION)!' > prototype.bcfg2.fixed
- -cat prototype.bcfg2-server | sed -e 's!PYVERSION!python$(PYVERSION)!' > prototype.bcfg2-server.fixed
- -pkgmk -o -a `uname -m` -f prototype.bcfg2.fixed -d $(PWD)/tmp -r $(PWD)
- -pkgmk -o -a `uname -m` -f prototype.bcfg2-server.fixed -d $(PWD)/tmp -r $(PWD)
+ -./gen-prototypes.sh
+ -pkgmk -o -a `uname -m` -f prototype.bcfg2 -d $(PWD)/tmp -r $(PWD)/build
+ -pkgmk -o -a `uname -m` -f prototype.bcfg2-server -d $(PWD)/tmp -r $(PWD)/build
-pkgtrans -o -s $(PWD)/tmp $(PWD)/bcfg2-$(VERS) SCbcfg2
-pkgtrans -o -s $(PWD)/tmp $(PWD)/bcfg2-server-$(VERS) SCbcfg2-server
-gzip -f $(PWD)/bcfg2-$(VERS)
-gzip -f $(PWD)/bcfg2-server-$(VERS)
clean:
- -rm -rf tmp bin lib share
+ -rm -rf tmp build
-rm -rf bcfg2-$(VERS).gz bcfg2-server-$(VERS).gz
-rm -rf prototype.bcfg2.fixed prototype.bcfg2-server.fixed
+ -rm -f prototype.*
diff --git a/solaris/gen-prototypes.sh b/solaris/gen-prototypes.sh
new file mode 100644
index 000000000..ea0b4bb13
--- /dev/null
+++ b/solaris/gen-prototypes.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+cd build
+PP="./"`ls -1d lib/*`"/site-packages/"
+
+#bcfg2
+echo "i pkginfo=./pkginfo.bcfg2" > ../prototype.tmp
+find . | grep man[15] | pkgproto >> ../prototype.tmp
+echo "./bin" | pkgproto >> ../prototype.tmp
+echo "./bin/bcfg2" | pkgproto >> ../prototype.tmp
+echo "${PP}Bcfg2" | pkgproto >> ../prototype.tmp
+ls -1 ${PP}Bcfg2/*.py | pkgproto >> ../prototype.tmp
+find ${PP}Bcfg2/Client/ ! -name "*.pyc" | pkgproto >> ../prototype.tmp
+sed "s/`id | sed 's/uid=[0-9]*(\(.*\)) gid=[0-9]*(\(.*\))/\1 \2/'`/bin bin/" ../prototype.tmp > ../prototype.bcfg2
+
+#bcfg2-server
+echo "i pkginfo=./pkginfo.bcfg2-server" > ../prototype.tmp
+find . | grep man8 | pkgproto >> ../prototype.tmp
+find share/bcfg2 | pkgproto >> ../prototype.tmp
+echo "./bin" | pkgproto >> ../prototype.tmp
+ls -1 bin/bcfg2-* | pkgproto >> ../prototype.tmp
+find ${PP}Bcfg2/Server/ ! -name "*.pyc" | pkgproto >> ../prototype.tmp
+sed "s/`id | sed 's/uid=[0-9]*(\(.*\)) gid=[0-9]*(\(.*\))/\1 \2/'`/bin bin/" ../prototype.tmp > ../prototype.bcfg2-server
+
+rm ../prototype.tmp
diff --git a/solaris/pkginfo.bcfg2 b/solaris/pkginfo.bcfg2
index aa57a7c19..0ff18516d 100644
--- a/solaris/pkginfo.bcfg2
+++ b/solaris/pkginfo.bcfg2
@@ -1,7 +1,7 @@
PKG="SCbcfg2"
NAME="bcfg2"
ARCH="sparc"
-VERSION="1.2.0"
+VERSION="1.2.2"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/solaris/pkginfo.bcfg2-server b/solaris/pkginfo.bcfg2-server
index 485eda903..a0958f9e4 100644
--- a/solaris/pkginfo.bcfg2-server
+++ b/solaris/pkginfo.bcfg2-server
@@ -1,7 +1,7 @@
PKG="SCbcfg2-server"
NAME="bcfg2-server"
ARCH="sparc"
-VERSION="1.2.0"
+VERSION="1.2.2"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
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 eca8960c1..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
@@ -240,25 +239,25 @@ class Frame:
if self.setup['remove'] == 'all':
self.removal = self.extra
elif self.setup['remove'] in ['services', 'Services']:
- self.removal = [entry for entry in self.extra \
+ self.removal = [entry for entry in self.extra
if entry.tag == 'Service']
elif self.setup['remove'] in ['packages', 'Packages']:
- self.removal = [entry for entry in self.extra \
+ self.removal = [entry for entry in self.extra
if entry.tag == 'Package']
- candidates = [entry for entry in self.states \
+ candidates = [entry for entry in self.states
if not self.states[entry]]
if self.dryrun:
if self.whitelist:
self.logger.info("In dryrun mode: suppressing entry installation for:")
- self.logger.info(["%s:%s" % (entry.tag, entry.get('name')) for entry \
- in self.whitelist])
+ self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
+ for entry in self.whitelist])
self.whitelist = []
if self.removal:
self.logger.info("In dryrun mode: suppressing entry removal for:")
- self.logger.info(["%s:%s" % (entry.tag, entry.get('name')) for entry \
- in self.removal])
+ self.logger.info(["%s:%s" % (entry.tag, entry.get('name'))
+ for entry in self.removal])
self.removal = []
return
# Here is where most of the work goes
@@ -270,13 +269,13 @@ class Frame:
for bundle in self.setup['bundle']:
if bundle not in all_bundle_names:
self.logger.info("Warning: Bundle %s not found" % bundle)
- bundles = [b for b in self.config.findall('./Bundle') \
+ bundles = [b for b in self.config.findall('./Bundle')
if b.get('name') in self.setup['bundle']]
- self.whitelist = [e for e in self.whitelist if \
- True in [e in b for b in bundles]]
+ self.whitelist = [e for e in self.whitelist
+ if True in [e in b for b in bundles]]
elif self.setup['indep']:
- bundles = [nb for nb in self.config.getchildren() if nb.tag != \
- 'Bundle']
+ bundles = [nb for nb in self.config.getchildren()
+ if nb.tag != 'Bundle']
else:
bundles = self.config.getchildren()
@@ -284,22 +283,24 @@ class Frame:
for bundle in bundles[:]:
if bundle.tag != 'Bundle':
continue
- actions = [a for a in bundle.findall('./Action') \
- if a.get('timing') != 'post']
- # now we process all "always actions"
bmodified = len([item for item in bundle if item in self.whitelist])
- for action in actions:
- if bmodified or action.get('when') == 'always':
- self.DispatchInstallCalls([action])
+ actions = [a for a in bundle.findall('./Action')
+ if (a.get('timing') != 'post' and
+ (bmodified or a.get('when') == 'always'))]
+ # now we process all "always actions"
+ 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
- self.logger.info("Bundle %s failed prerequisite action" % \
+ self.logger.info("Bundle %s failed prerequisite action" %
(bundle.get('name')))
bundles.remove(bundle)
b_to_remv = [ent for ent in self.whitelist if ent in bundle]
if b_to_remv:
- self.logger.info("Not installing entries from Bundle %s" % \
+ self.logger.info("Not installing entries from Bundle %s" %
(bundle.get('name')))
self.logger.info(["%s:%s" % (e.tag, e.get('name'))
for e in b_to_remv])
@@ -425,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 23b536451..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
@@ -11,7 +10,7 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool):
__handles__ = [('Package', 'macport')]
__req__ = {'Package': ['name', 'version']}
pkgtype = 'macport'
- pkgtool = ("/opt/local/bin/port install %s")
+ pkgtool = ('/opt/local/bin/port install %s', ('%s', ['name']))
def __init__(self, logger, setup, config):
Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config)
@@ -27,7 +26,7 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool):
continue
pkgname = pkg.split('@')[0].strip()
version = pkg.split('@')[1].split(' ')[0]
- self.logger.info(" pkgname: %s\n version: %s" % (pkgname, version))
+ self.logger.info(" pkgname: %s version: %s" % (pkgname, version))
self.installed[pkgname] = version
def VerifyPackage(self, entry, modlist):
@@ -38,13 +37,20 @@ class MacPorts(Bcfg2.Client.Tools.PkgTool):
return False
if entry.attrib['name'] in self.installed:
- if self.installed[entry.attrib['name']] == entry.attrib['version']:
+ if (self.installed[entry.attrib['name']] == entry.attrib['version'] or
+ entry.attrib['version'] == 'any'):
#if not self.setup['quick'] and \
# entry.get('verify', 'true') == 'true':
#FIXME: We should be able to check this once
# http://trac.macports.org/ticket/15709 is implemented
return True
else:
+ self.logger.info(" %s: Wrong version installed. "
+ "Want %s, but have %s" % (entry.get("name"),
+ entry.get("version"),
+ self.installed[entry.get("name")],
+ ))
+
entry.set('current_version', self.installed[entry.get('name')])
return False
entry.set('current_exists', 'false')
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 a018e68fb..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."""
@@ -146,21 +139,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
conflicts = ['YUM24', 'RPMng']
def __init__(self, logger, setup, config):
- self.yb = yum.YumBase()
-
- if setup['debug']:
- debuglevel = 3
- elif setup['verbose']:
- debuglevel = 2
- else:
- debuglevel = 1
-
- try:
- self.yb.preconf.debuglevel = debuglevel
- except AttributeError:
- self.yb._getConfig(self.yb.config_file_path,
- debuglevel=debuglevel)
-
+ self._loadYumBase(setup=setup, logger=logger)
Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config)
self.ignores = [entry.get('name') for struct in config \
for entry in struct \
@@ -179,18 +158,6 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
or entry.get('name') == '/etc/yum.conf']
self.yum_avail = dict()
self.yum_installed = dict()
- try:
- self.yb.doConfigSetup()
- self.yb.doTsSetup()
- self.yb.doRpmDBSetup()
- except yum.Errors.RepoError:
- e = sys.exc_info()[1]
- self.logger.error("YUMng Repository error: %s" % e)
- raise Bcfg2.Client.Tools.toolInstantiationError
- except Exception:
- e = sys.exc_info()[1]
- self.logger.error("YUMng error: %s" % e)
- raise Bcfg2.Client.Tools.toolInstantiationError
yup = self.yb.doPackageLists(pkgnarrow='updates')
if hasattr(self.yb.rpmdb, 'pkglist'):
@@ -211,6 +178,50 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
else:
dest[pname] = dict(data)
+ def _loadYumBase(self, setup=None, logger=None):
+ ''' this may be called before PkgTool.__init__() is called on
+ this object (when the YUMng object is first instantiated;
+ PkgTool.__init__() calls RefreshPackages(), which requires a
+ YumBase object already exist), or after __init__() has
+ completed, when we reload the yum config before installing
+ packages. Consequently, we support both methods by allowing
+ setup and logger, the only object properties we use in this
+ function, to be passed as keyword arguments or to be omitted
+ and drawn from the object itself.'''
+ self.yb = yum.YumBase()
+
+ if setup is None:
+ setup = self.setup
+ if logger is None:
+ logger = self.logger
+
+ if setup['debug']:
+ debuglevel = 3
+ elif setup['verbose']:
+ debuglevel = 2
+ else:
+ debuglevel = 0
+
+ try:
+ self.yb.preconf.debuglevel = debuglevel
+ self.yb._getConfig()
+ except AttributeError:
+ self.yb._getConfig(self.yb.conf.config_file_path,
+ debuglevel=debuglevel)
+
+ try:
+ self.yb.doConfigSetup()
+ self.yb.doTsSetup()
+ self.yb.doRpmDBSetup()
+ except yum.Errors.RepoError:
+ err = sys.exc_info()[1]
+ logger.error("YUMng Repository error: %s" % err)
+ raise Bcfg2.Client.Tools.toolInstantiationError
+ except Exception:
+ err = sys.exc_info()[1]
+ logger.error("YUMng error: %s" % err)
+ raise Bcfg2.Client.Tools.toolInstantiationError
+
def _loadConfig(self):
# Process the YUMng section from the config file.
CP = Parser()
@@ -742,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
@@ -841,6 +853,10 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
pkg = self.instance_status[gpg_keys[0]].get('pkg')
states[pkg] = self.VerifyPackage(pkg, [])
+ # We want to reload all Yum configuration in case we've
+ # deployed new .repo files we should consider
+ self._loadYumBase()
+
# Install packages.
if len(install_pkgs) > 0:
self.logger.info("Attempting to install packages")
diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py
index 9d0c69892..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__)) \
@@ -288,6 +283,10 @@ class SvcTool(Tool):
"""This class defines basic Service behavior"""
name = 'SvcTool'
+ def __init__(self, logger, setup, config):
+ Tool.__init__(self, logger, setup, config)
+ self.restarted = []
+
def get_svc_command(self, service, action):
"""Return the basename of the command used to start/stop services."""
return '/etc/init.d/%s %s' % (service.get('name'), action)
@@ -309,6 +308,13 @@ class SvcTool(Tool):
# not supported for this driver
return 0
+ def Remove(self, services):
+ """ Dummy implementation of service removal method """
+ if self.setup['servicemode'] != 'disabled':
+ for entry in services:
+ entry.set("status", "off")
+ self.InstallService(entry)
+
def BundleUpdated(self, bundle, states):
"""The Bundle has been updated."""
if self.setup['servicemode'] == 'disabled':
@@ -316,17 +322,20 @@ class SvcTool(Tool):
for entry in [ent for ent in bundle if self.handlesEntry(ent)]:
mode = entry.get('mode', 'default')
- if mode == 'manual' or \
- (mode == 'interactive_only' and not self.setup['interactive']):
+ if (mode == 'manual' or
+ (mode == 'interactive_only' and
+ not self.setup['interactive'])):
continue
# need to handle servicemode = (build|default)
# need to handle mode = (default|supervised)
+ rc = None
if entry.get('status') == 'on':
if self.setup['servicemode'] == 'build':
rc = self.stop_service(entry)
- else:
+ elif entry.get('name') not in self.restarted:
if self.setup['interactive']:
- prompt = 'Restart service %s?: (y/N): ' % entry.get('name')
+ prompt = ('Restart service %s?: (y/N): ' %
+ entry.get('name'))
# py3k compatibility
try:
ans = raw_input(prompt)
@@ -335,8 +344,10 @@ class SvcTool(Tool):
if ans not in ['y', 'Y']:
continue
rc = self.restart_service(entry)
+ if not rc:
+ self.restarted.append(entry.get('name'))
else:
rc = self.stop_service(entry)
if rc:
- self.logger.error("Failed to manipulate service %s" % \
+ self.logger.error("Failed to manipulate service %s" %
(entry.get('name')))
diff --git a/src/lib/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py
index 03dd97e71..c022d32ae 100644
--- a/src/lib/Client/Tools/launchd.py
+++ b/src/lib/Bcfg2/Client/Tools/launchd.py
@@ -1,8 +1,6 @@
"""launchd support for Bcfg2."""
-__revision__ = '$Revision$'
import os
-import popen2
import Bcfg2.Client.Tools
@@ -27,7 +25,8 @@ class launchd(Bcfg2.Client.Tools.Tool):
/Library/LaunchDaemons System wide daemons provided by the administrator.
/System/Library/LaunchAgents Mac OS X Per-user agents.
/System/Library/LaunchDaemons Mac OS X System wide daemons.'''
- plistLocations = ["/Library/LaunchDaemons", "/System/Library/LaunchDaemons"]
+ plistLocations = ["/Library/LaunchDaemons",
+ "/System/Library/LaunchDaemons"]
self.plistMapping = {}
for directory in plistLocations:
for daemon in os.listdir(directory):
@@ -36,11 +35,12 @@ class launchd(Bcfg2.Client.Tools.Tool):
d = daemon[:-6]
else:
d = daemon
- (stdout, _) = popen2.popen2('defaults read %s/%s Label' % (directory, d))
- label = stdout.read().strip()
+ label = self.cmd.run('defaults read %s/%s Label' %
+ (directory, d))[1][0]
self.plistMapping[label] = "%s/%s" % (directory, daemon)
- except KeyError: #perhaps this could be more robust
- pass
+ except KeyError:
+ self.logger.warning("Could not get label from %s/%s" %
+ (directory, daemon))
def FindPlist(self, entry):
return self.plistMapping.get(entry.get('name'), None)
@@ -61,20 +61,26 @@ class launchd(Bcfg2.Client.Tools.Tool):
"""Verify launchd service entry."""
try:
services = self.cmd.run("/bin/launchctl list")[1]
- except IndexError:#happens when no services are running (should be never)
+ except IndexError:
+ # happens when no services are running (should be never)
services = []
# launchctl output changed in 10.5
- # It is now three columns, with the last column being the name of the # service
+ # It is now three columns, with the last
+ # column being the name of the # service
version = self.os_version()
if version.startswith('10.5') or version.startswith('10.6'):
services = [s.split()[-1] for s in services]
- if entry.get('name') in services:#doesn't check if non-spawning services are Started
+ if entry.get('name') in services:
+ # doesn't check if non-spawning services are Started
return entry.get('status') == 'on'
else:
- self.logger.debug("Didn't find service Loaded (launchd running under same user as bcfg)")
+ self.logger.debug("Launchd: Didn't find service Loaded "
+ "(launchd running under same user as bcfg)")
return entry.get('status') == 'off'
- try: #Perhaps add the "-w" flag to load and unload to modify the file itself!
+ try:
+ # Perhaps add the "-w" flag to load and
+ # unload to modify the file itself!
self.cmd.run("/bin/launchctl load -w %s" % self.FindPlist(entry))
except IndexError:
return 'on'
@@ -90,12 +96,14 @@ class launchd(Bcfg2.Client.Tools.Tool):
name = entry.get('name')
if entry.get('status') == 'on':
self.logger.error("Installing service %s" % name)
- cmdrc = self.cmd.run("/bin/launchctl load -w %s" % self.FindPlist(entry))
+ cmdrc = self.cmd.run("/bin/launchctl load -w %s" %
+ self.FindPlist(entry))
cmdrc = self.cmd.run("/bin/launchctl start %s" % name)
else:
self.logger.error("Uninstalling service %s" % name)
cmdrc = self.cmd.run("/bin/launchctl stop %s" % name)
- cmdrc = self.cmd.run("/bin/launchctl unload -w %s" % self.FindPlist(entry))
+ cmdrc = self.cmd.run("/bin/launchctl unload -w %s" %
+ self.FindPlist(entry))
return cmdrc[0] == 0
def Remove(self, svcs):
@@ -120,17 +128,23 @@ class launchd(Bcfg2.Client.Tools.Tool):
"""Reload launchd plist."""
for entry in [entry for entry in bundle if self.handlesEntry(entry)]:
if not self.canInstall(entry):
- self.logger.error("Insufficient information to restart service %s" % (entry.get('name')))
+ self.logger.error("Insufficient information to restart service %s" %
+ (entry.get('name')))
else:
name = entry.get('name')
if entry.get('status') == 'on' and self.FindPlist(entry):
self.logger.info("Reloading launchd service %s" % name)
- #stop?
+ # stop?
self.cmd.run("/bin/launchctl stop %s" % name)
- self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry)))#what if it disappeared? how do we stop services that are currently running but the plist disappeared?!
- self.cmd.run("/bin/launchctl load -w %s" % (self.FindPlist(entry)))
+ # what if it disappeared? how do we stop services
+ # that are currently running but the plist disappeared?!
+ self.cmd.run("/bin/launchctl unload -w %s" %
+ (self.FindPlist(entry)))
+ self.cmd.run("/bin/launchctl load -w %s" %
+ (self.FindPlist(entry)))
self.cmd.run("/bin/launchctl start %s" % name)
else:
- #only if necessary....
+ # only if necessary....
self.cmd.run("/bin/launchctl stop %s" % name)
- self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry)))
+ self.cmd.run("/bin/launchctl unload -w %s" %
+ (self.FindPlist(entry)))
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 0acafb24c..06aae615e 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
@@ -88,7 +87,7 @@ class FragmentingSysLogHandler(logging.handlers.SysLogHandler):
record.exc_info = None
msgdata = record.msg
while msgdata:
- newrec = copy.deepcopy(record)
+ newrec = copy.copy(record)
newrec.msg = msgdata[:250]
msgs.append(newrec)
msgdata = msgdata[250:]
diff --git a/src/lib/Options.py b/src/lib/Bcfg2/Options.py
index fcd9107a9..f6273924a 100644
--- a/src/lib/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -1,9 +1,9 @@
"""Option parsing library for utilities."""
-__revision__ = '$Revision$'
import getopt
import os
import sys
+import shlex
import Bcfg2.Client.Tools
# Compatibility imports
from Bcfg2.Bcfg2Py3k import ConfigParser
@@ -111,6 +111,7 @@ class Option(object):
self.value = self.get_cooked_value(os.environ[self.env])
return
if self.cf:
+ # FIXME: This is potentially masking a lot of errors
try:
self.value = self.get_cooked_value(self.cfp.get(*self.cf))
return
@@ -133,7 +134,12 @@ class OptionSet(dict):
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:
@@ -347,7 +353,16 @@ CLIENT_SERVICE_MODE = Option('Set client service mode', default='default',
CLIENT_TIMEOUT = Option('Set the client XML-RPC timeout', default=90,
cmd='-t', cf=('communication', 'timeout'),
odesc='<timeout>')
-
+
+# bcfg2-test options
+TEST_NOSEOPTS = Option('Options to pass to nosetests', default=[],
+ cmd='--nose-options', cf=('bcfg2_test', 'nose_options'),
+ odesc='<opts>', long_arg=True, cook=shlex.split)
+TEST_IGNORE = Option('Ignore these entries if they fail to build.', default=[],
+ cmd='--ignore',
+ cf=('bcfg2_test', 'ignore_entries'), long_arg=True,
+ odesc='<Type>:<name>,<Type>:<name>', cook=list_split)
+
# APT client tool options
CLIENT_APT_TOOLS_INSTALL_PATH = Option('Apt tools install path',
cf=('APT', 'install_path'),
@@ -373,3 +388,9 @@ class OptionParser(OptionSet):
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)
diff --git a/src/lib/Proxy.py b/src/lib/Bcfg2/Proxy.py
index e1406bd99..2e653f47e 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
@@ -22,10 +19,12 @@ import socket
try:
import ssl
SSL_LIB = 'py26_ssl'
+ SSL_ERROR = ssl.SSLError
except ImportError:
from M2Crypto import SSL
import M2Crypto.SSL.Checker
SSL_LIB = 'm2crypto'
+ SSL_ERROR = SSL.SSLError
import sys
@@ -49,18 +48,20 @@ class ProxyError(Exception):
the various xmlrpclib errors that might arise (mainly
ProtocolError and Fault) """
def __init__(self, err):
+ msg = None
if isinstance(err, xmlrpclib.ProtocolError):
# cut out the password in the URL
url = re.sub(r'([^:]+):(.*?)@([^@]+:\d+/)', r'\1:******@\3',
err.url)
- self.message = "XML-RPC Protocol Error for %s: %s (%s)" % \
- (url, err.errmsg, err.errcode)
+ msg = "XML-RPC Protocol Error for %s: %s (%s)" % (url,
+ err.errmsg,
+ err.errcode)
elif isinstance(err, xmlrpclib.Fault):
- self.message = "XML-RPC Fault: %s (%s)" % (err.faultString,
- err.faultCode)
+ msg = "XML-RPC Fault: %s (%s)" % (err.faultString,
+ err.faultCode)
else:
- self.message = str(err)
- self.args = (self.message, )
+ msg = str(err)
+ Exception(self, msg)
class CertificateError(Exception):
def __init__(self, commonName):
@@ -298,25 +299,20 @@ class XMLRPCTransport(xmlrpclib.Transport):
def request(self, host, handler, request_body, verbose=0):
"""Send request to server and return response."""
h = self.make_connection(host)
- self.send_request(h, handler, request_body)
- self.send_host(h, host)
- self.send_user_agent(h)
- self.send_content(h, request_body)
-
- if SSL_LIB == 'py26_ssl':
- catch = ssl.SSLError
- elif SSL_LIB == 'm2crypto':
- catch = SSL.SSLError
try:
+ self.send_request(h, handler, request_body)
+ self.send_host(h, host)
+ self.send_user_agent(h)
+ self.send_content(h, request_body)
errcode, errmsg, headers = h.getreply()
- except catch:
+ except (socket.error, SSL_ERROR):
err = sys.exc_info()[1]
raise ProxyError(xmlrpclib.ProtocolError(host + handler,
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..418e259cc 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",
]
diff --git a/src/lib/Server/Admin/Backup.py b/src/lib/Bcfg2/Server/Admin/Backup.py
index 9bd644ff9..3744abca3 100644
--- a/src/lib/Server/Admin/Backup.py
+++ b/src/lib/Bcfg2/Server/Admin/Backup.py
@@ -12,17 +12,9 @@ class Backup(Bcfg2.Server.Admin.MetadataCore):
#"\n\nbcfg2-admin backup restore")
__usage__ = ("bcfg2-admin backup")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.MetadataCore.__init__(self, configfile,
- self.__usage__)
-
def __call__(self, args):
Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
- # Get Bcfg2 repo directory
- opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY}
- setup = Bcfg2.Options.OptionParser(opts)
- setup.parse(sys.argv[1:])
- self.datastore = setup['repo']
+ self.datastore = self.setup['repo']
timestamp = time.strftime('%Y%m%d%H%M%S')
format = 'gz'
mode = 'w:' + format
diff --git a/src/lib/Server/Admin/Bundle.py b/src/lib/Bcfg2/Server/Admin/Bundle.py
index 9b2a71783..89c099602 100644
--- a/src/lib/Server/Admin/Bundle.py
+++ b/src/lib/Bcfg2/Server/Admin/Bundle.py
@@ -15,19 +15,13 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore):
"\nbcfg2-admin bundle show\n")
__usage__ = ("bcfg2-admin bundle [options] [add|del] [group]")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.MetadataCore.__init__(self, configfile,
- self.__usage__)
-
def __call__(self, args):
Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
- reg = '((?:[a-z][a-z\\.\\d\\-]+)\\.(?:[a-z][a-z\\-]+))(?![\\w\\.])'
+ rg = re.compile(r'([^.]+\.(?:[a-z][a-z\-]+))(?![\w\.])',
+ re.IGNORECASE | re.DOTALL)
# Get all bundles out of the Bundle/ directory
- opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY}
- setup = Bcfg2.Options.OptionParser(opts)
- setup.parse(sys.argv[1:])
- repo = setup['repo']
+ repo = self.setup['repo']
xml_list = glob.glob("%s/Bundler/*.xml" % repo)
genshi_list = glob.glob("%s/Bundler/*.genshi" % repo)
@@ -50,7 +44,6 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore):
elif args[0] in ['list-xml', 'ls-xml']:
bundle_name = []
for bundle_path in xml_list:
- rg = re.compile(reg, re.IGNORECASE | re.DOTALL)
bundle_name.append(rg.search(bundle_path).group(1))
for bundle in bundle_name:
print(bundle.split('.')[0])
@@ -58,7 +51,6 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore):
elif args[0] in ['list-genshi', 'ls-gen']:
bundle_name = []
for bundle_path in genshi_list:
- rg = re.compile(reg, re.IGNORECASE | re.DOTALL)
bundle_name.append(rg.search(bundle_path).group(1))
for bundle in bundle_name:
print(bundle.split('.')[0])
@@ -71,7 +63,7 @@ class Bundle(Bcfg2.Server.Admin.MetadataCore):
bundle_name = []
bundle_list = xml_list + genshi_list
for bundle_path in bundle_list:
- rg = re.compile(reg, re.IGNORECASE | re.DOTALL)
+ print "matching %s" % bundle_path
bundle_name.append(rg.search(bundle_path).group(1))
text = "Available bundles (Number of bundles: %s)" % \
(len(bundle_list))
diff --git a/src/lib/Server/Admin/Client.py b/src/lib/Bcfg2/Server/Admin/Client.py
index c746374a2..4d580c54c 100644
--- a/src/lib/Server/Admin/Client.py
+++ b/src/lib/Bcfg2/Server/Admin/Client.py
@@ -13,10 +13,6 @@ class Client(Bcfg2.Server.Admin.MetadataCore):
"\nbcfg2-admin client del <client>\n")
__usage__ = ("bcfg2-admin client [options] [add|del|update|list] [attr=val]")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.MetadataCore.__init__(self, configfile,
- self.__usage__)
-
def __call__(self, args):
Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
if len(args) == 0:
diff --git a/src/lib/Server/Admin/Compare.py b/src/lib/Bcfg2/Server/Admin/Compare.py
index 82d0d690c..050dd69f8 100644
--- a/src/lib/Server/Admin/Compare.py
+++ b/src/lib/Bcfg2/Server/Admin/Compare.py
@@ -12,8 +12,8 @@ class Compare(Bcfg2.Server.Admin.Mode):
__usage__ = ("bcfg2-admin compare <old> <new>\n\n"
" -r\trecursive")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.Mode.__init__(self, configfile)
+ def __init__(self, setup):
+ Bcfg2.Server.Admin.Mode.__init__(self, setup)
self.important = {'Path': ['name', 'type', 'owner', 'group', 'perms',
'important', 'paranoid', 'sensitive',
'dev_type', 'major', 'minor', 'prune',
diff --git a/src/lib/Server/Admin/Group.py b/src/lib/Bcfg2/Server/Admin/Group.py
index 1c5d0c12f..16a773d6f 100644
--- a/src/lib/Server/Admin/Group.py
+++ b/src/lib/Bcfg2/Server/Admin/Group.py
@@ -13,10 +13,6 @@ class Group(Bcfg2.Server.Admin.MetadataCore):
"\nbcfg2-admin group del <group>\n")
__usage__ = ("bcfg2-admin group [options] [add|del|update|list] [attr=val]")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.MetadataCore.__init__(self, configfile,
- self.__usage__)
-
def __call__(self, args):
Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
if len(args) == 0:
diff --git a/src/lib/Server/Admin/Init.py b/src/lib/Bcfg2/Server/Admin/Init.py
index aba6bbd32..c1f9ed484 100644
--- a/src/lib/Server/Admin/Init.py
+++ b/src/lib/Bcfg2/Server/Admin/Init.py
@@ -30,8 +30,6 @@ database_password =
database_host =
# Not used with sqlite3.
database_port =
-# Set to empty string for default. Not used with sqlite3.
-web_debug = True
[communication]
protocol = %s
@@ -57,6 +55,7 @@ groups = '''<Groups version='3.0'>
<Group name='suse'/>
<Group name='mandrake'/>
<Group name='solaris'/>
+ <Group name='arch'/>
</Groups>
'''
@@ -73,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',
@@ -175,9 +175,6 @@ class Init(Bcfg2.Server.Admin.Mode):
repopath = ""
response = ""
- def __init__(self, configfile):
- Bcfg2.Server.Admin.Mode.__init__(self, configfile)
-
def _set_defaults(self):
"""Set default parameters."""
self.configfile = self.opts['configfile']
diff --git a/src/lib/Server/Admin/Minestruct.py b/src/lib/Bcfg2/Server/Admin/Minestruct.py
index abe1d5a7a..b929a9a8c 100644
--- a/src/lib/Server/Admin/Minestruct.py
+++ b/src/lib/Bcfg2/Server/Admin/Minestruct.py
@@ -18,10 +18,6 @@ class Minestruct(Bcfg2.Server.Admin.StructureMode):
"-g <groups>",
"only build config for groups"))
- def __init__(self, configfile):
- Bcfg2.Server.Admin.StructureMode.__init__(self, configfile,
- self.__usage__)
-
def __call__(self, args):
Bcfg2.Server.Admin.Mode.__call__(self, args)
if len(args) == 0:
diff --git a/src/lib/Server/Admin/Perf.py b/src/lib/Bcfg2/Server/Admin/Perf.py
index d03b37d57..411442698 100644
--- a/src/lib/Server/Admin/Perf.py
+++ b/src/lib/Bcfg2/Server/Admin/Perf.py
@@ -10,9 +10,6 @@ class Perf(Bcfg2.Server.Admin.Mode):
__longhelp__ = (__shorthelp__ + "\n\nbcfg2-admin perf\n")
__usage__ = ("bcfg2-admin perf")
- def __init__(self, configfile):
- Bcfg2.Server.Admin.Mode.__init__(self, configfile)
-
def __call__(self, args):
output = [('Name', 'Min', 'Max', 'Mean', 'Count')]
optinfo = {
@@ -25,7 +22,7 @@ class Perf(Bcfg2.Server.Admin.Mode):
'timeout': Bcfg2.Options.CLIENT_TIMEOUT,
}
setup = Bcfg2.Options.OptionParser(optinfo)
- setup.parse(sys.argv[2:])
+ setup.parse(sys.argv[1:])
proxy = Bcfg2.Proxy.ComponentProxy(setup['server'],
setup['user'],
setup['password'],
diff --git a/src/lib/Server/Admin/Pull.py b/src/lib/Bcfg2/Server/Admin/Pull.py
index 47a8be253..daf353107 100644
--- a/src/lib/Server/Admin/Pull.py
+++ b/src/lib/Bcfg2/Server/Admin/Pull.py
@@ -28,14 +28,13 @@ class Pull(Bcfg2.Server.Admin.MetadataCore):
"stdin"))
allowed = ['Metadata', 'BB', "DBStats", "Statistics", "Cfg", "SSHbase"]
- def __init__(self, configfile):
- Bcfg2.Server.Admin.MetadataCore.__init__(self, configfile,
- self.__usage__)
+ def __init__(self, setup):
+ Bcfg2.Server.Admin.MetadataCore.__init__(self, setup)
self.log = False
self.mode = 'interactive'
def __call__(self, args):
- Bcfg2.Server.Admin.Mode.__call__(self, args)
+ Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
use_stdin = False
try:
opts, gargs = getopt.getopt(args, 'vfIs')
diff --git a/src/lib/Server/Admin/Query.py b/src/lib/Bcfg2/Server/Admin/Query.py
index 9e1d7cc88..3dd326645 100644
--- a/src/lib/Server/Admin/Query.py
+++ b/src/lib/Bcfg2/Server/Admin/Query.py
@@ -1,9 +1,10 @@
+import sys
import logging
import Bcfg2.Logger
import Bcfg2.Server.Admin
-class Query(Bcfg2.Server.Admin.Mode):
+class Query(Bcfg2.Server.Admin.MetadataCore):
__shorthelp__ = "Query clients"
__longhelp__ = (__shorthelp__ + "\n\nbcfg2-admin query [-n] [-c] "
"[-f filename] g=group p=profile")
@@ -18,23 +19,14 @@ class Query(Bcfg2.Server.Admin.Mode):
"-f filename",
"write query to file"))
- def __init__(self, cfile):
+ def __init__(self, setup):
+ Bcfg2.Server.Admin.MetadataCore.__init__(self, setup)
logging.root.setLevel(100)
Bcfg2.Logger.setup_logging(100, to_console=False, to_syslog=False)
- Bcfg2.Server.Admin.Mode.__init__(self, cfile)
- try:
- self.bcore = Bcfg2.Server.Core.Core(self.get_repo_path(),
- ['Metadata', 'Probes'],
- 'foo', False, 'UTF-8')
- except Bcfg2.Server.Core.CoreInitError:
- msg = sys.exc_info()[1]
- self.errExit("Core load failed because %s" % msg)
- self.bcore.fam.handle_events_in_interval(1)
- self.meta = self.bcore.metadata
def __call__(self, args):
- Bcfg2.Server.Admin.Mode.__call__(self, args)
- clients = list(self.meta.clients.keys())
+ Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
+ clients = list(self.metadata.clients.keys())
filename_arg = False
filename = None
for arg in args:
@@ -53,9 +45,9 @@ class Query(Bcfg2.Server.Admin.Mode):
print("Unknown argument %s" % arg)
continue
if k == 'p':
- nc = self.meta.get_client_names_by_profiles(v.split(','))
+ nc = self.metadata.get_client_names_by_profiles(v.split(','))
elif k == 'g':
- nc = self.meta.get_client_names_by_groups(v.split(','))
+ nc = self.metadata.get_client_names_by_groups(v.split(','))
# add probed groups (if present)
for conn in self.bcore.connectors:
if isinstance(conn, Bcfg2.Server.Plugins.Probes.Probes):
diff --git a/src/lib/Server/Admin/Reports.py b/src/lib/Bcfg2/Server/Admin/Reports.py
index 3f25f11af..974cdff9d 100644
--- a/src/lib/Server/Admin/Reports.py
+++ b/src/lib/Bcfg2/Server/Admin/Reports.py
@@ -8,9 +8,6 @@ import pickle
import platform
import sys
import traceback
-from Bcfg2.Server.Reports.importscript import load_stats
-from Bcfg2.Server.Reports.updatefix import update_database
-from Bcfg2.Server.Reports.utils import *
from lxml.etree import XML, XMLSyntaxError
# Compatibility import
@@ -22,16 +19,15 @@ if sys.version_info >= (2, 5):
else:
from md5 import md5
-# Load django
-import django.core.management
-
+# Prereq issues can be signaled with ImportError, so no try needed
# FIXME - settings file uses a hardcoded path for /etc/bcfg2.conf
-try:
- import Bcfg2.Server.Reports.settings
-except Exception:
- e = sys.exc_info()[1]
- sys.stderr.write("Failed to load configuration settings. %s\n" % e)
- raise SystemExit(1)
+import Bcfg2.Server.Reports.settings
+
+# Load django and reports stuff _after_ we know we can load settings
+import django.core.management
+from Bcfg2.Server.Reports.importscript import load_stats
+from Bcfg2.Server.Reports.updatefix import update_database
+from Bcfg2.Server.Reports.utils import *
project_directory = os.path.dirname(Bcfg2.Server.Reports.settings.__file__)
project_name = os.path.basename(project_directory)
@@ -81,6 +77,7 @@ class Reports(Bcfg2.Server.Admin.Mode):
'''Admin interface for dynamic reports'''
__shorthelp__ = "Manage dynamic reports"
__longhelp__ = (__shorthelp__)
+ django_commands = ['syncdb', 'sqlall', 'validate']
__usage__ = ("bcfg2-admin reports [command] [options]\n"
" -v|--verbose Be verbose\n"
" -q|--quiet Print only errors\n"
@@ -97,14 +94,13 @@ class Reports(Bcfg2.Server.Admin.Mode):
" --expired Expired clients only\n"
" scrub Scrub the database for duplicate reasons and orphaned entries\n"
" update Apply any updates to the reporting database\n"
- "\n")
+ "\n"
+ " Django commands:\n "
+ "\n ".join(django_commands))
- def __init__(self, cfile):
- Bcfg2.Server.Admin.Mode.__init__(self, cfile)
+ def __init__(self, setup):
+ Bcfg2.Server.Admin.Mode.__init__(self, setup)
self.log.setLevel(logging.INFO)
- self.django_commands = ['syncdb', 'sqlall', 'validate']
- self.__usage__ = self.__usage__ + " Django commands:\n " + \
- "\n ".join(self.django_commands)
def __call__(self, args):
Bcfg2.Server.Admin.Mode.__call__(self, args)
diff --git a/src/lib/Server/Admin/Snapshots.py b/src/lib/Bcfg2/Server/Admin/Snapshots.py
index 052545b61..8bc56f1f1 100644
--- a/src/lib/Server/Admin/Snapshots.py
+++ b/src/lib/Bcfg2/Server/Admin/Snapshots.py
@@ -23,11 +23,10 @@ class Snapshots(Bcfg2.Server.Admin.Mode):
'package': Package,
'snapshot': Snapshot}
- def __init__(self, configfile):
- Bcfg2.Server.Admin.Mode.__init__(self, configfile)
- #self.session = Bcfg2.Server.Snapshots.setup_session(debug=True)
- self.session = Bcfg2.Server.Snapshots.setup_session(configfile)
- self.cfile = configfile
+ def __init__(self, setup):
+ Bcfg2.Server.Admin.Mode.__init__(self, setup)
+ self.session = Bcfg2.Server.Snapshots.setup_session(self.configfile)
+ self.cfile = self.configfile
def __call__(self, args):
Bcfg2.Server.Admin.Mode.__call__(self, args)
diff --git a/src/lib/Server/Admin/Tidy.py b/src/lib/Bcfg2/Server/Admin/Tidy.py
index f79991fd9..82319b93e 100644
--- a/src/lib/Server/Admin/Tidy.py
+++ b/src/lib/Bcfg2/Server/Admin/Tidy.py
@@ -16,9 +16,6 @@ class Tidy(Bcfg2.Server.Admin.Mode):
"-I",
"interactive"))
- def __init__(self, cfile):
- Bcfg2.Server.Admin.Mode.__init__(self, cfile)
-
def __call__(self, args):
Bcfg2.Server.Admin.Mode.__call__(self, args)
badfiles = self.buildTidyList()
@@ -49,7 +46,7 @@ class Tidy(Bcfg2.Server.Admin.Mode):
bad = []
# clean up unresolvable hosts in SSHbase
- for name in os.listdir("%s/SSHbase" % (self.get_repo_path())):
+ for name in os.listdir("%s/SSHbase" % self.setup['repo']):
if hostmatcher.match(name):
hostname = hostmatcher.match(name).group(1)
if hostname in good + bad:
@@ -59,14 +56,14 @@ class Tidy(Bcfg2.Server.Admin.Mode):
good.append(hostname)
except:
bad.append(hostname)
- for name in os.listdir("%s/SSHbase" % (self.get_repo_path())):
+ for name in os.listdir("%s/SSHbase" % self.setup['repo']):
if not hostmatcher.match(name):
- to_remove.append("%s/SSHbase/%s" % (self.get_repo_path(),
+ to_remove.append("%s/SSHbase/%s" % (self.setup['repo'],
name))
else:
if hostmatcher.match(name).group(1) in bad:
to_remove.append("%s/SSHbase/%s" %
- (self.get_repo_path(), name))
+ (self.setup['repo'], name))
# clean up file~
# clean up files without parsable names in Cfg
return to_remove
diff --git a/src/lib/Server/Admin/Viz.py b/src/lib/Bcfg2/Server/Admin/Viz.py
index 2c618634a..2faa423c1 100644
--- a/src/lib/Server/Admin/Viz.py
+++ b/src/lib/Bcfg2/Server/Admin/Viz.py
@@ -32,16 +32,10 @@ class Viz(Bcfg2.Server.Admin.MetadataCore):
'indianred1', 'limegreen', 'orange1', 'lightblue2',
'green1', 'blue1', 'yellow1', 'darkturquoise', 'gray66']
- plugin_blacklist = ['DBStats', 'Snapshots', 'Cfg', 'Pkgmgr', 'Packages',
- 'Rules', 'Account', 'Decisions', 'Deps', 'Git', 'Svn',
- 'Fossil', 'Bzr', 'Bundler', 'TGenshi', 'SGenshi',
- 'Base']
-
- def __init__(self, cfile):
-
- Bcfg2.Server.Admin.MetadataCore.__init__(self, cfile,
- self.__usage__,
- pblacklist=self.plugin_blacklist)
+ __plugin_blacklist__ = ['DBStats', 'Snapshots', 'Cfg', 'Pkgmgr', 'Packages',
+ 'Rules', 'Account', 'Decisions', 'Deps', 'Git',
+ 'Svn', 'Fossil', 'Bzr', 'Bundler', 'TGenshi',
+ 'SGenshi', 'Base']
def __call__(self, args):
Bcfg2.Server.Admin.MetadataCore.__call__(self, args)
@@ -73,7 +67,7 @@ class Viz(Bcfg2.Server.Admin.MetadataCore):
elif opt in ("-o", "--outfile"):
outputfile = arg
- data = self.Visualize(self.get_repo_path(), hset, bset,
+ data = self.Visualize(self.setup['repo'], hset, bset,
kset, only_client, outputfile)
if data:
print(data)
diff --git a/src/lib/Server/Admin/Xcmd.py b/src/lib/Bcfg2/Server/Admin/Xcmd.py
index 8faf68368..140465468 100644
--- a/src/lib/Server/Admin/Xcmd.py
+++ b/src/lib/Bcfg2/Server/Admin/Xcmd.py
@@ -51,5 +51,10 @@ class Xcmd(Bcfg2.Server.Admin.Mode):
return
else:
raise
+ except Bcfg2.Proxy.ProxyError:
+ err = sys.exc_info()[1]
+ print("Proxy Error: %s" % err)
+ return
+
if data != None:
print(data)
diff --git a/src/lib/Server/Admin/__init__.py b/src/lib/Bcfg2/Server/Admin/__init__.py
index 96d9703ba..fdb9a0972 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',
@@ -8,7 +6,6 @@ __all__ = [
'Group',
'Init',
'Minestruct',
- 'Mode',
'Perf',
'Pull',
'Query',
@@ -37,12 +34,16 @@ class Mode(object):
"""Help message has not yet been added for mode."""
__shorthelp__ = 'Shorthelp not defined yet'
__longhelp__ = 'Longhelp not defined yet'
+ __usage__ = None
__args__ = []
- def __init__(self, configfile):
- self.configfile = configfile
+ def __init__(self, setup):
+ self.setup = setup
+ self.configfile = setup['configfile']
self.__cfp = False
self.log = logging.getLogger('Bcfg2.Server.Admin.Mode')
+ if self.__usage__ is not None:
+ setup.hm = self.__usage__
def getCFP(self):
if not self.__cfp:
@@ -59,16 +60,8 @@ class Mode(object):
print(emsg)
raise SystemExit(1)
- def get_repo_path(self):
- """Return repository path"""
- try:
- return self.cfp.get('server', 'repository')
- except ConfigParser.NoSectionError:
- self.errExit("Unable to find server section in bcfg2.conf")
-
def load_stats(self, client):
- stats = lxml.etree.parse("%s/etc/statistics.xml" %
- (self.get_repo_path()))
+ stats = lxml.etree.parse("%s/etc/statistics.xml" % self.setup['repo'])
hostent = stats.xpath('//Node[@name="%s"]' % client)
if not hostent:
self.errExit("Could not find stats for client %s" % (client))
@@ -111,27 +104,30 @@ class Mode(object):
class MetadataCore(Mode):
"""Base class for admin-modes that handle metadata."""
- def __init__(self, configfile, usage, pwhitelist=None, pblacklist=None):
- Mode.__init__(self, configfile)
- options = {'plugins': Bcfg2.Options.SERVER_PLUGINS,
- 'configfile': Bcfg2.Options.CFILE,
- 'encoding': Bcfg2.Options.ENCODING}
- setup = Bcfg2.Options.OptionParser(options)
- setup.hm = usage
- setup.parse(sys.argv[1:])
- if pwhitelist is not None:
- setup['plugins'] = [x for x in setup['plugins']
- if x in pwhitelist]
- elif pblacklist is not None:
- setup['plugins'] = [x for x in setup['plugins']
- if x not in pblacklist]
+ __plugin_whitelist__ = None
+ __plugin_blacklist__ = None
+
+ def __init__(self, setup):
+ Mode.__init__(self, setup)
+ if self.__plugin_whitelist__ is not None:
+ setup['plugins'] = [p for p in setup['plugins']
+ if p in self.__plugin_whitelist__]
+ elif self.__plugin_blacklist__ is not None:
+ setup['plugins'] = [p for p in setup['plugins']
+ if p not in self.__plugin_blacklist__]
+
try:
- self.bcore = Bcfg2.Server.Core.Core(self.get_repo_path(),
- setup['plugins'],
- 'foo', setup['encoding'])
+ self.bcore = \
+ Bcfg2.Server.Core.Core(setup['repo'],
+ setup['plugins'],
+ setup['password'],
+ setup['encoding'],
+ filemonitor=setup['filemonitor'])
+ if setup['event debug']:
+ self.bcore.fam.debug = True
except Bcfg2.Server.Core.CoreInitError:
msg = sys.exc_info()[1]
- self.errExit("Core load failed because %s" % msg)
+ self.errExit("Core load failed: %s" % msg)
self.bcore.fam.handle_events_in_interval(5)
self.metadata = self.bcore.metadata
diff --git a/src/lib/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 05625cf22..4321c060b 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
@@ -7,6 +6,8 @@ import select
import sys
import threading
import time
+from traceback import format_exc
+
try:
import lxml.etree
except ImportError:
@@ -37,6 +38,14 @@ except:
pass
+def sort_xml(node, key=None):
+ for child in node:
+ sort_xml(child, key)
+
+ sorted_children = sorted(node, key=key)
+ node[:] = sorted_children
+
+
class CoreInitError(Exception):
"""This error is raised when the core cannot be initialized."""
pass
@@ -232,12 +241,12 @@ class Core(Component):
self.Bind(entry, metadata)
except PluginExecutionError, exc:
if 'failure' not in entry.attrib:
- entry.set('failure', 'bind error: %s' % exc)
+ entry.set('failure', 'bind error: %s' % format_exc())
logger.error("Failed to bind entry: %s %s" % \
(entry.tag, entry.get('name')))
except Exception, exc:
if 'failure' not in entry.attrib:
- entry.set('failure', 'bind error: %s' % exc)
+ entry.set('failure', 'bind error: %s' % format_exc())
logger.error("Unexpected failure in BindStructure: %s %s" \
% (entry.tag, entry.get('name')), exc_info=1)
@@ -275,7 +284,8 @@ class Core(Component):
if len(g2list) == 1:
return g2list[0].HandleEntry(entry, metadata)
entry.set('failure', 'no matching generator')
- raise PluginExecutionError(entry.tag, entry.get('name'))
+ raise PluginExecutionError("No matching generator: %s:%s" %
+ (entry.tag, entry.get('name')))
def BuildConfiguration(self, client):
"""Build configuration for clients."""
@@ -315,6 +325,9 @@ 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" % \
(client, time.time() - start))
return config
@@ -376,9 +389,9 @@ class Core(Component):
return lxml.etree.tostring(resp, encoding='UTF-8',
xml_declaration=True)
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- warning = 'Client metadata resolution error for %s; check server log' % address[0]
+ warning = 'Client metadata resolution error for %s' % address[0]
self.logger.warning(warning)
- raise xmlrpclib.Fault(6, warning)
+ raise xmlrpclib.Fault(6, warning + "; check server log")
except Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError:
err_msg = 'Metadata system runtime failure'
self.logger.error(err_msg)
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..44626b462 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,10 @@ class Bundles(Bcfg2.Server.Lint.ServerPlugin):
Bcfg2.Server.Plugins.SGenshi.SGenshiTemplateFile):
self.bundle_names(bundle)
+ def Errors(self):
+ 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 +44,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..e16469bb5 100644
--- a/src/lib/Server/Lint/Comments.py
+++ b/src/lib/Bcfg2/Server/Lint/Comments.py
@@ -16,6 +16,12 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin):
self.check_infoxml()
self.check_probes()
+ def Errors(self):
+ 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 +37,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]:
@@ -128,7 +134,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 +161,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/Bcfg2/Server/Lint/Deltas.py b/src/lib/Bcfg2/Server/Lint/Deltas.py
new file mode 100644
index 000000000..8d35d8e5a
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Lint/Deltas.py
@@ -0,0 +1,23 @@
+import Bcfg2.Server.Lint
+
+class Deltas(Bcfg2.Server.Lint.ServerPlugin):
+ """ Warn about usage of .cat and .diff files """
+ def Run(self):
+ """ run plugin """
+ if 'Cfg' in self.core.plugins:
+ cfg = self.core.plugins['Cfg']
+ for basename, entry in list(cfg.entries.items()):
+ self.check_entry(basename, entry)
+
+ def Errors(self):
+ 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))
diff --git a/src/lib/Server/Lint/Duplicates.py b/src/lib/Bcfg2/Server/Lint/Duplicates.py
index 75f620603..abc581c4f 100644
--- a/src/lib/Server/Lint/Duplicates.py
+++ b/src/lib/Bcfg2/Server/Lint/Duplicates.py
@@ -22,6 +22,13 @@ class Duplicates(Bcfg2.Server.Lint.ServerPlugin):
if self.clients_xdata is not None:
self.duplicate_clients()
+ def Errors(self):
+ 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 +37,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 552c495b2..45ddf8f2f 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,16 +11,21 @@ class Genshi(Bcfg2.Server.Lint.ServerPlugin):
self.check_files(self.core.plugins[plugin].entries,
loader=loader)
+ def Errors(self):
+ 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 fname.endswith(".genshi") or fname.endswith(".newtxt"):
+ if (self.HandlesFile(fname) and
+ (fname.endswith(".genshi") or fname.endswith(".newtxt"))):
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/Bcfg2/Server/Lint/GroupPatterns.py b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py
new file mode 100644
index 000000000..f50118ce1
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py
@@ -0,0 +1,34 @@
+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 """
+
+ def Run(self):
+ """ run plugin """
+ if 'GroupPatterns' in self.core.plugins:
+ cfg = self.core.plugins['GroupPatterns'].config
+ for entry in cfg.xdata.xpath('//GroupPattern'):
+ groups = [g.text for g in entry.findall('Group')]
+ self.check(entry, groups, ptype='NamePattern')
+ self.check(entry, groups, ptype='NameRange')
+
+ def Errors(self):
+ 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:
+ pmap(pat)
+ except:
+ err = sys.exc_info()[1]
+ self.LintError("pattern-fails-to-initialize",
+ "Failed to initialize %s %s for %s: %s" %
+ (ptype, pat, entry.get('pattern'), err))
diff --git a/src/lib/Server/Lint/InfoXML.py b/src/lib/Bcfg2/Server/Lint/InfoXML.py
index 2054e23bf..20c218e6f 100644
--- a/src/lib/Server/Lint/InfoXML.py
+++ b/src/lib/Bcfg2/Server/Lint/InfoXML.py
@@ -4,7 +4,6 @@ import Bcfg2.Server.Lint
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():
@@ -18,6 +17,12 @@ class InfoXML(Bcfg2.Server.Lint.ServerPlugin):
self.LintError("no-infoxml",
"No info.xml found for %s" % filename)
+ def Errors(self):
+ 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 52fea3d9b..3259aca44 100644
--- a/src/lib/Server/Lint/MergeFiles.py
+++ b/src/lib/Bcfg2/Server/Lint/MergeFiles.py
@@ -1,18 +1,22 @@
import os
-from copy import deepcopy
+import copy
from difflib import SequenceMatcher
import Bcfg2.Server.Lint
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()
+ def Errors(self):
+ 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):
@@ -26,7 +30,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]))
@@ -43,7 +47,7 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
rv = []
elist = entries.items()
while elist:
- result = self._find_similar(elist.pop(0), deepcopy(elist),
+ result = self._find_similar(elist.pop(0), copy.copy(elist),
threshold)
if len(result) > 1:
elist = [(fname, fdata)
@@ -62,8 +66,8 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
if (sm.real_quick_ratio() > threshold and
sm.quick_ratio() > threshold and
sm.ratio() > threshold):
- rv.extend(self._find_similar((cname, cdata), deepcopy(others),
+ rv.extend(self._find_similar((cname, cdata), copy.copy(others),
threshold))
return rv
-
+
diff --git a/src/lib/Server/Lint/Pkgmgr.py b/src/lib/Bcfg2/Server/Lint/Pkgmgr.py
index 3960a8cf9..7c17d555a 100644
--- a/src/lib/Server/Lint/Pkgmgr.py
+++ b/src/lib/Bcfg2/Server/Lint/Pkgmgr.py
@@ -1,21 +1,18 @@
+import glob
+import lxml.etree
import Bcfg2.Server.Lint
-class Pkgmgr(Bcfg2.Server.Lint.ServerPlugin):
+class Pkgmgr(Bcfg2.Server.Lint.ServerlessPlugin):
""" find duplicate Pkgmgr entries with the same priority """
-
def Run(self):
- if 'Pkgmgr' not in self.core.plugins:
- self.logger.info("Pkgmgr server plugin is not enabled, skipping Pkgmgr lint checks")
- return
-
pset = set()
- for plist in self.core.plugins['Pkgmgr'].entries.values():
- if self.HandlesFile(plist.name):
- xdata = plist.data
+ for pfile in glob.glob("%s/Pkgmgr/*.xml" % self.config['repo']):
+ if self.HandlesFile(pfile):
+ xdata = lxml.etree.parse(pfile).getroot()
# get priority, type, group
- priority = xdata.getroot().get('priority')
- ptype = xdata.getroot().get('type')
- for pkg in xdata.findall("//Package"):
+ priority = xdata.get('priority')
+ ptype = xdata.get('type')
+ for pkg in xdata.xpath("//Package"):
if pkg.getparent().tag == 'Group':
grp = pkg.getparent().get('name')
if (type(grp) is not str and
@@ -35,3 +32,6 @@ class Pkgmgr(Bcfg2.Server.Lint.ServerPlugin):
(pkg.get('name'), priority, ptype))
else:
pset.add(ptuple)
+
+ def Errors(self):
+ return {"duplicate-packages":"error"}
diff --git a/src/lib/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
index 55206d2ba..20947d50f 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,13 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
self.check_rules()
self.check_bundles()
+ def Errors(self):
+ 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..f5f916e88
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py
@@ -0,0 +1,63 @@
+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))
+
+ def Errors(self):
+ 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..812ab3d00 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,15 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
self.check_properties()
+ def Errors(self):
+ 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 +107,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 +189,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 f47059ac4..f3991e3fc 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',
@@ -8,7 +6,8 @@ __all__ = ['Bundles',
'Pkgmgr',
'RequiredAttrs',
'Validate',
- 'Genshi']
+ 'Genshi',
+ 'Deltas']
import logging
import os
@@ -55,14 +54,19 @@ 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
+ def Errors(self):
+ """ 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 """
@@ -74,8 +78,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"""
@@ -92,34 +96,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"}
-
def __init__(self, config=None):
self.errors = 0
self.warnings = 0
@@ -128,11 +104,12 @@ class ErrorHandler (object):
termsize = get_termsize()
if termsize is not None:
- self._wrapper = textwrap.TextWrapper(initial_indent=" ",
- subsequent_indent=" ",
- width=termsize[0])
+ twrap = textwrap.TextWrapper(initial_indent=" ",
+ subsequent_indent=" ",
+ width=termsize[0])
+ self._wrapper = twrap.wrap
else:
- self._wrapper = None
+ self._wrapper = lambda s: [s]
self._handlers = {}
if config is not None:
@@ -144,7 +121,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
@@ -152,6 +130,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:
@@ -187,13 +166,10 @@ class ErrorHandler (object):
rawlines = msg.splitlines()
firstline = True
for rawline in rawlines:
- if self._wrapper:
- lines = self._wrapper.wrap(rawline)
- else:
- lines = [rawline]
+ lines = self._wrapper(rawline)
for line in lines:
if firstline:
- logfunc("%s%s" % (prefix, line.lstrip()))
+ logfunc(prefix + line.lstrip())
firstline = False
else:
logfunc(line)
diff --git a/src/lib/Server/Plugin.py b/src/lib/Bcfg2/Server/Plugin.py
index 3dc1cab38..11e6c5c20 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
@@ -11,6 +10,7 @@ import posixpath
import re
import sys
import threading
+from Bcfg2.Bcfg2Py3k import ConfigParser
from lxml.etree import XML, XMLSyntaxError
@@ -62,11 +62,28 @@ class PluginExecutionError(Exception):
pass
-class Plugin(object):
+class Debuggable(object):
+ __rmi__ = ['toggle_debug']
+
+ def __init__(self, name=None):
+ if name is None:
+ name = "%s.%s" % (self.__class__.__module__,
+ self.__class__.__name__)
+ self.debug_flag = False
+ self.logger = logging.getLogger(name)
+
+ def toggle_debug(self):
+ self.debug_flag = not self.debug_flag
+
+ def debug_log(self, message, flag=None):
+ if (flag is None and self.debug_flag) or flag:
+ self.logger.error(message)
+
+
+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:
@@ -75,9 +92,7 @@ class Plugin(object):
- Data collection (overloading GetProbes/ReceiveData)
"""
name = 'Plugin'
- __version__ = '$Id$'
__author__ = 'bcfg-dev@mcs.anl.gov'
- __rmi__ = ['toggle_debug']
experimental = False
deprecated = False
conflicts = []
@@ -89,24 +104,16 @@ class Plugin(object):
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.logger = logging.getLogger('Bcfg2.Plugins.%s' % (self.name))
+ self.data = os.path.join(datastore, self.name)
self.running = True
- self.debug_flag = False
-
- def toggle_debug(self):
- self.debug_flag = not self.debug_flag
-
- def debug_log(self, message, flag=None):
- if (flag is None) and self.debug_flag or flag:
- self.logger.error(message)
+ Debuggable.__init__(self, name=self.name)
@classmethod
def init_repo(cls, repo):
@@ -282,7 +289,7 @@ class ThreadedStatistics(Statistics,
def process_statistics(self, metadata, data):
warned = False
try:
- self.work_queue.put_nowait((metadata, copy.deepcopy(data)))
+ self.work_queue.put_nowait((metadata, copy.copy(data)))
warned = False
except Full:
if not warned:
@@ -372,6 +379,12 @@ class FileBacked(object):
"""Update local data structures based on current file state"""
pass
+ def __repr__(self):
+ return "%s: %s" % (self.__class__.__name__, str(self))
+
+ def __str__(self):
+ return "%s: %s" % (self.name, self.data)
+
class DirectoryBacked(object):
"""This object is a coherent cache for a filesystem hierarchy of files."""
@@ -385,7 +398,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)
@@ -443,7 +456,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.
@@ -567,6 +580,9 @@ class XMLFileBacked(FileBacked):
def __iter__(self):
return iter(self.entries)
+ def __str__(self):
+ return "%s: %s" % (self.name, lxml.etree.tostring(self.xdata))
+
class SingleXMLFileBacked(XMLFileBacked):
"""This object is a coherent cache for an independent XML file."""
@@ -576,6 +592,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:
@@ -585,22 +624,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__]
@@ -609,7 +640,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)
@@ -636,13 +667,13 @@ class StructFile(XMLFileBacked):
rv.extend(self._match(child, metadata))
return rv
else:
- rv = copy.deepcopy(item)
+ rv = copy.copy(item)
for child in rv.iterchildren():
rv.remove(child)
for child in item.iterchildren():
rv.extend(self._match(child, metadata))
return [rv]
-
+
def Match(self, metadata):
"""Return matching fragments of independent."""
rv = []
@@ -770,6 +801,9 @@ class XMLSrc(XMLFileBacked):
self.pnode.Match(metadata, cache[1])
self.cache = cache
+ def __str__(self):
+ return str(self.items)
+
class InfoXML (XMLSrc):
__node__ = InfoNode
@@ -813,21 +847,19 @@ 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 list(self.entries.values()):
- cached = src.Cache(metadata)
- if cached == False:
- self.logger.error("Called before data loaded")
- raise PluginExecutionError
+ for src in self.entries.values():
+ src.Cache(metadata)
+
matching = [src for src in list(self.entries.values())
if (src.cache and
entry.tag in src.cache[1] and
self._matches(entry, metadata,
src.cache[1][entry.tag]))]
if len(matching) == 0:
- raise PluginExecutionError
+ raise PluginExecutionError('No matching source for entry when retrieving attributes for %s(%s)' % (entry.tag, entry.attrib.get('name')))
elif len(matching) == 1:
index = 0
else:
@@ -849,7 +881,7 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
if '__text__' in data:
entry.text = data['__text__']
if '__children__' in data:
- [entry.append(copy.deepcopy(item)) for item in data['__children__']]
+ [entry.append(copy.copy(item)) for item in data['__children__']]
return dict([(key, data[key])
for key in list(data.keys())
@@ -857,7 +889,6 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
# new unified EntrySet backend
-
class SpecificityError(Exception):
"""Thrown in case of filename parse failure."""
pass
@@ -919,7 +950,7 @@ class SpecificData(object):
logger.error("Failed to read file %s" % self.name)
-class EntrySet:
+class EntrySet(object):
"""Entry sets deal with the host- and group-specific entries."""
ignore = re.compile("^(\.#.*|.*~|\\..*\\.(sw[px])|.*\\.genshi_include)$")
@@ -973,6 +1004,12 @@ class EntrySet:
self.entry_init(event)
else:
if event.filename not in self.entries:
+ logger.warning("Got %s event for unknown file %s" %
+ (action, event.filename))
+ if action == 'changed':
+ # received a bogus changed event; warn, but treat
+ # it like a created event
+ self.entry_init(event)
return
if action == 'changed':
self.entries[event.filename].handle_event(event)
@@ -989,7 +1026,8 @@ class EntrySet:
spec = self.specificity_from_filename(event.filename)
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)
@@ -1069,7 +1107,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
@@ -1086,41 +1123,63 @@ class GroupSpool(Plugin, Generator):
self.AddDirectoryMonitor('')
self.encoding = core.encoding
+ def add_entry(self, event):
+ epath = self.event_path(event)
+ ident = self.event_id(event)
+ if posixpath.isdir(epath):
+ self.AddDirectoryMonitor(epath[len(self.data):])
+ if ident not in self.entries and posixpath.isfile(epath):
+ dirpath = "".join([self.data, ident])
+ self.entries[ident] = self.es_cls(self.filename_pattern,
+ dirpath,
+ self.es_child_cls,
+ self.encoding)
+ self.Entries['Path'][ident] = self.entries[ident].bind_entry
+ if not posixpath.isdir(epath):
+ # do not pass through directory events
+ self.entries[ident].handle_event(event)
+
+ def event_path(self, event):
+ return "".join([self.data, self.handles[event.requestID],
+ event.filename])
+
+ def event_id(self, event):
+ epath = self.event_path(event)
+ if posixpath.isdir(epath):
+ return self.handles[event.requestID] + event.filename
+ else:
+ return self.handles[event.requestID][:-1]
+
def HandleEvent(self, event):
- """Unified FAM event handler for DirShadow."""
+ """Unified FAM event handler for GroupSpool."""
action = event.code2str()
if event.filename[0] == '/':
return
- epath = "".join([self.data, self.handles[event.requestID],
- event.filename])
- if posixpath.isdir(epath):
- ident = self.handles[event.requestID] + event.filename
- else:
- ident = self.handles[event.requestID][:-1]
+ ident = self.event_id(event)
if action in ['exists', 'created']:
- if posixpath.isdir(epath):
- self.AddDirectoryMonitor(epath[len(self.data):])
- if ident not in self.entries and posixpath.isfile(epath):
- dirpath = "".join([self.data, ident])
- self.entries[ident] = self.es_cls(self.filename_pattern,
- dirpath,
- self.es_child_cls,
- self.encoding)
- self.Entries['Path'][ident] = self.entries[ident].bind_entry
- if not posixpath.isdir(epath):
- # do not pass through directory events
+ self.add_entry(event)
+ if action == 'changed':
+ if ident in self.entries:
self.entries[ident].handle_event(event)
- if action == 'changed' and ident in self.entries:
- self.entries[ident].handle_event(event)
+ else:
+ # got a changed event for a file we didn't know
+ # about. go ahead and process this as a 'created', but
+ # warn
+ self.logger.warning("Got changed event for unknown file %s" %
+ ident)
+ self.add_entry(event)
elif action == 'deleted':
fbase = self.handles[event.requestID] + event.filename
if fbase in self.entries:
# a directory was deleted
del self.entries[fbase]
del self.Entries['Path'][fbase]
- else:
+ elif ident in self.entries:
self.entries[ident].handle_event(event)
+ elif ident not in self.entries:
+ self.logger.warning("Got deleted event for unknown file %s" %
+ ident)
def AddDirectoryMonitor(self, relative):
"""Add new directory to FAM structures."""
@@ -1133,3 +1192,56 @@ 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
+ 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(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
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 137142b66..c015ec47c 100644
--- a/src/lib/Server/Plugins/BB.py
+++ b/src/lib/Bcfg2/Server/Plugins/BB.py
@@ -62,7 +62,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 5de57a87c..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
@@ -41,5 +39,5 @@ class Base(Bcfg2.Server.Plugin.Plugin,
fragments = reduce(lambda x, y: x + y,
[base.Match(metadata) for base
in list(self.entries.values())], [])
- [ret.append(copy.deepcopy(frag)) for frag in fragments]
+ [ret.append(copy.copy(frag)) for frag in fragments]
return [ret]
diff --git a/src/lib/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py
index 4b73a17d4..ccb99481e 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
@@ -24,7 +23,7 @@ class BundleFile(Bcfg2.Server.Plugin.StructFile):
def get_xml_value(self, metadata):
bundlename = os.path.splitext(os.path.basename(self.name))[0]
bundle = lxml.etree.Element('Bundle', name=bundlename)
- [bundle.append(copy.deepcopy(item)) for item in self.Match(metadata)]
+ [bundle.append(copy.copy(item)) for item in self.Match(metadata)]
return bundle
@@ -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)$')
@@ -75,23 +73,27 @@ class Bundler(Bcfg2.Server.Plugin.Plugin,
def BuildStructures(self, metadata):
"""Build all structures for client (metadata)."""
bundleset = []
+
+ bundle_entries = {}
+ for key, item in self.entries.items():
+ bundle_entries.setdefault(self.patterns.match(os.path.basename(key)).group('name'),
+ []).append(item)
+
for bundlename in metadata.bundles:
- entries = [item for (key, item) in self.entries.items() if \
- self.patterns.match(os.path.basename(key)).group('name') == bundlename]
- if len(entries) == 0:
+ try:
+ entries = bundle_entries[bundlename]
+ except KeyError:
+ self.logger.error("Bundler: Bundle %s does not exist" %
+ bundlename)
continue
- elif len(entries) == 1:
- try:
- bundleset.append(entries[0].get_xml_value(metadata))
- except genshi.template.base.TemplateError:
- t = sys.exc_info()[1]
- self.logger.error("Bundler: Failed to template genshi bundle %s" \
- % (bundlename))
- self.logger.error(t)
- except:
- self.logger.error("Bundler: Unexpected bundler error for %s" \
- % (bundlename), exc_info=1)
- else:
- self.logger.error("Got multiple matches for bundle %s" \
- % (bundlename))
+ try:
+ bundleset.append(entries[0].get_xml_value(metadata))
+ except genshi.template.base.TemplateError:
+ t = sys.exc_info()[1]
+ self.logger.error("Bundler: Failed to template genshi bundle %s"
+ % bundlename)
+ self.logger.error(t)
+ except:
+ self.logger.error("Bundler: Unexpected bundler error for %s" %
+ bundlename, exc_info=1)
return bundleset
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/Server/Plugins/Cfg.py b/src/lib/Bcfg2/Server/Plugins/Cfg.py
index 0a791f171..81904d082 100644
--- a/src/lib/Server/Plugins/Cfg.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg.py
@@ -1,5 +1,4 @@
"""This module implements a config file repository."""
-__revision__ = '$Revision$'
import binascii
import logging
@@ -31,6 +30,7 @@ try:
except:
have_cheetah = False
+# setup logging
logger = logging.getLogger('Bcfg2.Plugins.Cfg')
@@ -63,7 +63,7 @@ def process_delta(data, delta):
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]
@@ -100,6 +100,10 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
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)
@@ -115,8 +119,9 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
base_files = [matching.index(m) for m in matching
if not m.specific.delta]
if not base_files:
- logger.error("No base file found for %s" % entry.get('name'))
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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()
@@ -128,15 +133,16 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
basefile = used.pop(0)
if entry.get('perms').lower() == 'inherit':
# use on-disk permissions
- fname = "%s/%s" % (self.path, entry.get('name'))
+ 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:
- logger.error("Cfg: Genshi is not available")
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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()
@@ -154,13 +160,15 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
if data == '':
entry.set('empty', 'true')
except Exception:
- e = sys.exc_info()[1]
- logger.error("Cfg: genshi exception: %s" % e)
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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:
- logger.error("Cfg: Cheetah is not available")
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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}
@@ -173,9 +181,10 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
if data == '':
entry.set('empty', 'true')
except Exception:
- e = sys.exc_info()[1]
- logger.error("Cfg: cheetah exception: %s" % e)
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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:
@@ -186,17 +195,18 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
try:
entry.text = u_str(data, self.encoding)
except UnicodeDecodeError:
- e = sys.exc_info()[1]
- logger.error("Failed to decode %s: %s" % (entry.get('name'), e))
+ 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
+ raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
except ValueError:
- e = sys.exc_info()[1]
- logger.error("Error in specification for %s" % entry.get('name'))
- logger.error("%s" % e)
+ 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
+ raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
if entry.text in ['', None]:
entry.set('empty', 'true')
@@ -223,22 +233,34 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
if 'text' in new_entry:
name = self.build_filename(specific)
if os.path.exists("%s.genshi" % name):
- logger.error("Cfg: Unable to pull data for genshi types")
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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):
- logger.error("Cfg: Unable to pull data for cheetah types")
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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:
- logger.error("Cfg: Cannot encode content of %s as %s" % (name, self.encoding))
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ 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)
- if log:
- logger.info("Wrote file %s" % name)
+ 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:
@@ -250,15 +272,14 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
ofile = open(self.path + "/info.xml", "w")
ofile.write(lxml.etree.tostring(infoxml, pretty_print=True))
ofile.close()
- if log:
- logger.info("Wrote file %s" % (self.path + "/info.xml"))
+ 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
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 23104946e..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
@@ -49,3 +47,7 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules,
finally:
if is_bound:
entry.tag = "Bound" + entry.tag
+
+ def _regex_enabled(self):
+ """ Defaults depends on regex matching, so force it enabled """
+ return True
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 269664ef4..5beec7be0 100644
--- a/src/lib/Server/Plugins/FileProbes.py
+++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
@@ -3,9 +3,9 @@ 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
import errno
import binascii
import lxml.etree
@@ -54,7 +54,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):
@@ -89,33 +88,27 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin,
interpreter="/usr/bin/env python")
probe.text = probecode % path
self.probes[metadata.hostname].append(probe)
- self.logger.debug("Adding file probe for %s to %s" %
- (path, metadata.hostname))
+ self.debug_log("Adding file probe for %s to %s" %
+ (path, metadata.hostname))
return self.probes[metadata.hostname]
def ReceiveData(self, metadata, datalist):
"""Receive data from probe."""
- self.logger.debug("Receiving file probe data from %s" %
- metadata.hostname)
+ self.debug_log("Receiving file probe data from %s" % metadata.hostname)
for data in datalist:
if data.text is None:
self.logger.error("Got null response to %s file probe from %s" %
(data.get('name'), metadata.hostname))
else:
- self.logger.debug("%s:fileprobe:%s:%s" %
- (metadata.hostname,
- data.get("name"),
- data.text))
try:
- filedata = lxml.etree.XML(data.text)
- self.write_file(filedata, metadata)
+ self.write_data(lxml.etree.XML(data.text), metadata)
except lxml.etree.XMLSyntaxError:
# if we didn't get XML back from the probe, assume
# it's an error message
self.logger.error(data.text)
- def write_file(self, data, metadata):
+ def write_data(self, data, metadata):
"""Write the probed file data to the bcfg2 specification."""
filename = data.get("name")
contents = binascii.a2b_base64(data.text)
@@ -138,75 +131,81 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin,
else:
entrydata = entry.text
- if create:
- self.logger.info("Writing new probed file %s" % fileloc)
- try:
- os.makedirs(os.path.dirname(fileloc))
- except OSError, err:
- if err.errno == errno.EEXIST:
- pass
- else:
- raise
- open(fileloc, 'wb').write(contents)
-
- infoxml = os.path.join("%s%s" % (cfg.data, filename),
- "info.xml")
- if not os.path.exists(infoxml):
- self.write_infoxml(infoxml, entry, data)
-
- # Service the FAM events queued up by the key generation
- # so the data structure entries will be available for
- # binding.
- #
- # NOTE: We wait for up to ten seconds. There is some
- # potential for race condition, because if the file
- # monitor doesn't get notified about the new key files in
- # time, those entries won't be available for binding. In
- # practice, this seems "good enough".
- tries = 0
- is_bound = False
- while not is_bound:
- if tries >= 10:
- self.logger.error("%s still not registered" % filename)
- raise Bcfg2.Server.Plugin.PluginExecutionError
- self.core.fam.handle_events_in_interval(1)
- try:
- cfg.entries[filename].bind_entry(entry, metadata)
- is_bound = True
- except Bcfg2.Server.Plugin.PluginExecutionError:
- pass
- tries += 1
+ if create:
+ self.logger.info("Writing new probed file %s" % fileloc)
+ self.write_file(fileloc, contents)
+ self.verify_file(filename, contents, metadata)
+ infoxml = os.path.join("%s%s" % (cfg.data, filename), "info.xml")
+ self.write_infoxml(infoxml, entry, data)
elif entrydata == contents:
- self.logger.debug("Existing %s contents match probed contents" %
- filename)
+ self.debug_log("Existing %s contents match probed contents" %
+ filename)
return
elif (entry.get('update', 'false').lower() == "true"):
self.logger.info("Writing updated probed file %s" % fileloc)
- open(fileloc, 'wb').write(contents)
-
- # service FAM events
- tries = 0
- updated = False
- while not updated:
- if tries >= 10:
- self.logger.error("%s still not registered" % filename)
- raise Bcfg2.Server.Plugin.PluginExecutionError
- self.core.fam.handle_events_in_interval(1)
- cfg.entries[filename].bind_entry(entry, metadata)
- # get current entry data
- if entry.get("encoding") == "base64":
- entrydata = binascii.a2b_base64(entry.text)
- else:
- entrydata = entry.text
- if entrydata == contents:
- updated = True
- tries += 1
+ self.write_file(fileloc, contents)
+ self.verify_file(filename, contents, metadata)
else:
self.logger.info("Skipping updated probed file %s" % fileloc)
return
-
+
+ def write_file(self, fileloc, contents):
+ try:
+ os.makedirs(os.path.dirname(fileloc))
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EEXIST:
+ pass
+ else:
+ self.logger.error("Could not create parent directories for %s: "
+ "%s" % (fileloc, err))
+ return
+
+ try:
+ open(fileloc, 'wb').write(contents)
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Could not write %s: %s" % (fileloc, err))
+ return
+
+ def verify_file(self, filename, contents, metadata):
+ # Service the FAM events queued up by the key generation so
+ # the data structure entries will be available for binding.
+ #
+ # NOTE: We wait for up to ten seconds. There is some potential
+ # for race condition, because if the file monitor doesn't get
+ # notified about the new key files in time, those entries
+ # won't be available for binding. In practice, this seems
+ # "good enough".
+ entry = self.entries[metadata.hostname][filename]
+ cfg = self.core.plugins['Cfg']
+ tries = 0
+ updated = False
+ while not updated:
+ if tries >= 10:
+ self.logger.error("%s still not registered" % filename)
+ return
+ self.core.fam.handle_events_in_interval(1)
+ try:
+ cfg.entries[filename].bind_entry(entry, metadata)
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ tries += 1
+ continue
+
+ # get current entry data
+ if entry.get("encoding") == "base64":
+ entrydata = binascii.a2b_base64(entry.text)
+ else:
+ entrydata = entry.text
+ 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 = \
@@ -219,8 +218,13 @@ 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)
- open(infoxml, "w").write(lxml.etree.tostring(root,
- pretty_print=True))
+ try:
+ open(infoxml, "w").write(lxml.etree.tostring(root,
+ pretty_print=True))
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Could not write %s: %s" % (fileloc, err))
+ return
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 76a628931..58b4d4afb 100644
--- a/src/lib/Server/Plugins/GroupPatterns.py
+++ b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py
@@ -1,9 +1,8 @@
-import lxml.etree
import re
-
+import logging
+import lxml.etree
import Bcfg2.Server.Plugin
-
class PackedDigitRange(object):
def __init__(self, digit_range):
self.sparse = list()
@@ -25,7 +24,7 @@ class PackedDigitRange(object):
class PatternMap(object):
- range_finder = '\\[\\[[\d\-,]+\\]\\]'
+ range_finder = r'\[\[[\d\-,]+\]\]'
def __init__(self, pattern, rangestr, groups):
self.pattern = pattern
@@ -35,15 +34,18 @@ class PatternMap(object):
self.re = re.compile(pattern)
self.process = self.process_re
elif rangestr != None:
+ if '\\' in rangestr:
+ raise Exception("Backslashes are not allowed in NameRanges")
self.process = self.process_range
- self.re = re.compile('^' + re.subn(self.range_finder, '(\d+)',
- rangestr)[0])
- dmatcher = re.compile(re.subn(self.range_finder,
- '\\[\\[([\d\-,]+)\\]\\]',
- rangestr)[0])
- self.dranges = [PackedDigitRange(x) for x in dmatcher.match(rangestr).groups()]
+ self.re = re.compile('^' + re.sub(self.range_finder, '(\d+)',
+ rangestr))
+ dmatcher = re.compile(re.sub(self.range_finder,
+ r'\[\[([\d\-,]+)\]\]',
+ rangestr))
+ self.dranges = [PackedDigitRange(x)
+ for x in dmatcher.match(rangestr).groups()]
else:
- raise Exception
+ raise Exception("No pattern or range given")
def process_range(self, name):
match = self.re.match(name)
@@ -75,6 +77,7 @@ class PatternFile(Bcfg2.Server.Plugin.SingleXMLFileBacked):
def __init__(self, filename, fam):
Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, filename, fam)
self.patterns = []
+ self.logger = logging.getLogger(self.__class__.__name__)
def Index(self):
Bcfg2.Server.Plugin.SingleXMLFileBacked.Index(self)
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 8afcd9c50..970126b80 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
@@ -38,13 +36,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
@@ -64,43 +65,40 @@ 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):
"""Load changes from XML"""
try:
- xdata = lxml.etree.parse("%s/%s" % (self.basedir, self.basefile))
+ xdata = lxml.etree.parse(os.path.join(self.basedir, self.basefile))
except lxml.etree.XMLSyntaxError:
- self.logger.error('Failed to parse %s' % (self.basefile))
+ self.logger.error('Failed to parse %s' % self.basefile)
return
- self.basedata = copy.deepcopy(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.extras = []
+ self.basedata = copy.copy(xdata)
+ 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,40 +114,42 @@ 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:
"""Try to find the data in included files"""
for included in self.extras:
try:
- xdata = lxml.etree.parse("%s/%s" % (self.basedir, included))
+ xdata = lxml.etree.parse(os.path.join(self.basedir,
+ included))
cli = xdata.xpath(xpath)
if len(cli) > 0:
- return {'filename': "%s/%s" % (self.basedir, included),
+ return {'filename': os.path.join(self.basedir,
+ included),
'xmltree': xdata,
'xquery': cli}
except lxml.etree.XMLSyntaxError:
@@ -221,7 +221,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
@@ -232,8 +231,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
@@ -274,269 +273,241 @@ 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"))
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")
- 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")
- 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]
@@ -546,42 +517,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]:
@@ -589,13 +555,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:
@@ -620,7 +591,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
@@ -635,7 +607,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:
@@ -649,7 +622,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()
@@ -673,22 +647,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):
@@ -703,18 +682,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
@@ -732,7 +712,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'
@@ -764,10 +745,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:
@@ -775,14 +758,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':
@@ -793,13 +776,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."""
@@ -814,15 +791,16 @@ 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"))
try:
groups_tree.xinclude()
except lxml.etree.XIncludeError:
- self.logger.error("Failed to process XInclude for file %s" % dest)
+ self.logger.error("Failed to process XInclude for file %s: %s" %
+ (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:
@@ -842,10 +820,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')) \
@@ -854,8 +832,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':
@@ -864,24 +842,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 8a76c130d..4dbd57d16 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):
@@ -131,14 +130,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 ed954af3b..cbe2b4f2c 100644
--- a/src/lib/Server/Plugins/Packages/Apt.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
@@ -1,12 +1,9 @@
import re
import gzip
-import logging
from Bcfg2.Server.Plugins.Packages.Collection import Collection
from Bcfg2.Server.Plugins.Packages.Source import Source
from Bcfg2.Bcfg2Py3k import cPickle, file
-logger = logging.getLogger("Packages")
-
class AptCollection(Collection):
def get_group(self, group):
self.logger.warning("Packages: Package groups are not supported by APT")
@@ -15,6 +12,7 @@ class AptCollection(Collection):
class AptSource(Source):
basegroups = ['apt', 'debian', 'ubuntu', 'nexenta']
ptype = 'deb'
+ essentialpkgs = set()
def __init__(self, basepath, xsource, config):
Source.__init__(self, basepath, xsource, config)
@@ -53,10 +51,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
@@ -72,7 +69,7 @@ class AptSource(Source):
try:
reader = gzip.GzipFile(fname)
except:
- logger.error("Packages: Failed to read file %s" % fname)
+ self.logger.error("Packages: Failed to read file %s" % fname)
raise
for line in reader.readlines():
words = str(line.strip()).split(':', 1)
@@ -80,6 +77,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 f8cd8e690..959dac03b 100644
--- a/src/lib/Server/Plugins/Packages/Collection.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
@@ -1,13 +1,14 @@
import copy
import logging
+import Bcfg2.Server.Plugin
+
+logger = logging.getLogger(__name__)
try:
from hashlib import md5
except ImportError:
from md5 import md5
-logger = logging.getLogger("Packages")
-
# we have to cache Collection objects so that calling Packages.Refresh
# or .Reload can tell the collection objects to clean up their cache,
# but we don't actually use the cache to return a Collection object
@@ -20,12 +21,13 @@ logger = logging.getLogger("Packages")
# sources to that client.)
collections = dict()
-class Collection(object):
- def __init__(self, metadata, sources, basepath):
- """ don't call this directly; use the Factory method """
+class Collection(Bcfg2.Server.Plugin.Debuggable):
+ def __init__(self, metadata, sources, basepath, debug=False):
+ """ don't call this directly; use the factory function """
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ self.debug_flag = debug
self.metadata = metadata
self.sources = sources
- self.logger = logging.getLogger("Packages")
self.basepath = basepath
self.virt_pkgs = dict()
@@ -95,6 +97,12 @@ class Collection(object):
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)
@@ -193,14 +201,14 @@ class Collection(object):
# direct packages; current can be added, and all deps
# should be resolved
current = pkgs.pop()
- self.logger.debug("Packages: handling package requirement %s" %
- current)
+ self.debug_log("Packages: handling package requirement %s" %
+ current)
packages.add(current)
deps = self.get_deps(current)
newdeps = set(deps).difference(examined)
if newdeps:
- self.logger.debug("Packages: Package %s added "
- "requirements %s" % (current, newdeps))
+ self.debug_log("Packages: Package %s added requirements %s"
+ % (current, newdeps))
unclassified.update(newdeps)
satisfied_vpkgs = set()
@@ -208,16 +216,15 @@ class Collection(object):
# virtual dependencies, satisfied if one of N in the
# config, or can be forced if only one provider
if len(vpkg_cache[current]) == 1:
- self.logger.debug("Packages: requirement %s satisfied by "
- "%s" % (current,
- vpkg_cache[current]))
+ self.debug_log("Packages: requirement %s satisfied by %s" %
+ (current, vpkg_cache[current]))
unclassified.update(vpkg_cache[current].difference(examined))
satisfied_vpkgs.add(current)
else:
satisfiers = [item for item in vpkg_cache[current]
if item in packages]
- self.logger.debug("Packages: requirement %s satisfied by "
- "%s" % (current, satisfiers))
+ self.debug_log("Packages: requirement %s satisfied by %s" %
+ (current, satisfiers))
satisfied_vpkgs.add(current)
vpkgs.difference_update(satisfied_vpkgs)
@@ -230,8 +237,8 @@ class Collection(object):
satisfiers = [item for item in vpkg_cache[current]
if item in packages]
if satisfiers:
- self.logger.debug("Packages: requirement %s satisfied by "
- "%s" % (current, satisfiers))
+ self.debug_log("Packages: requirement %s satisfied by %s" %
+ (current, satisfiers))
satisfied_both.add(current)
elif current in packagelist or final_pass:
pkgs.add(current)
@@ -287,14 +294,14 @@ def clear_cache():
global collections
collections = dict()
-def factory(metadata, sources, basepath):
+def factory(metadata, sources, basepath, debug=False):
global collections
if not sources.loaded:
# if sources.xml has not received a FAM event yet, defer;
# instantiate a dummy Collection object
return Collection(metadata, [], basepath)
-
+
sclasses = set()
relevant = list()
@@ -314,7 +321,9 @@ def factory(metadata, sources, basepath):
# have multiple Bcfg2 servers and Packages-relevant groups set
# by probes); and b) templates that query all or multiple
# machines (e.g., with metadata.query.all_clients())
- logger.debug("Packages: No sources found for %s" % metadata.hostname)
+ if debug:
+ logger.error("Packages: No sources found for %s" %
+ metadata.hostname)
cclass = Collection
else:
stype = sclasses.pop().__name__.replace("Source", "")
@@ -330,10 +339,11 @@ def factory(metadata, sources, basepath):
logger.warning("Packages: No collection class found for %s sources"
% stype)
- logger.debug("Packages: Using %s for Collection of sources for %s" %
- (cclass.__name__, metadata.hostname))
+ if debug:
+ logger.error("Packages: Using %s for Collection of sources for %s" %
+ (cclass.__name__, metadata.hostname))
- collection = cclass(metadata, relevant, basepath)
+ collection = cclass(metadata, relevant, basepath, debug=debug)
collections[metadata.hostname] = collection
return collection
diff --git a/src/lib/Server/Plugins/Packages/Pac.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py
index 35fb39d02..99a090739 100644
--- a/src/lib/Server/Plugins/Packages/Pac.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py
@@ -1,15 +1,12 @@
import gzip
import tarfile
-import logging
from Bcfg2.Bcfg2Py3k import cPickle, file
from Bcfg2.Server.Plugins.Packages.Collection import Collection
from Bcfg2.Server.Plugins.Packages.Source import Source
-logger = logging.getLogger("Packages")
-
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):
@@ -54,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:
@@ -66,22 +62,23 @@ 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()
try:
- logger.debug("Packages: try to read : " + fname)
+ self.debug_log("Packages: try to read %s" % fname)
tar = tarfile.open(fname, "r")
reader = gzip.GzipFile(fname)
except:
- logger.error("Packages: Failed to read file %s" % fname)
+ self.logger.error("Packages: Failed to read file %s" % fname)
raise
for tarinfo in tar:
if tarinfo.isdir():
self.pkgnames.add(tarinfo.name.rsplit("-", 2)[0])
- logger.debug("Packages: added : " + tarinfo.name.rsplit("-", 2)[0])
+ self.debug_log("Packages: added %s" %
+ tarinfo.name.rsplit("-", 2)[0])
tar.close()
self.deps['global'] = dict()
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesConfig.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesConfig.py
new file mode 100644
index 000000000..3846c06ce
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesConfig.py
@@ -0,0 +1,15 @@
+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/Plugins/Packages/PackagesSources.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
index 4fbccab30..8d0067b6a 100644
--- a/src/lib/Server/Plugins/Packages/PackagesSources.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
@@ -1,17 +1,16 @@
import os
import sys
import lxml.etree
-import logging
import Bcfg2.Server.Plugin
from Bcfg2.Server.Plugins.Packages.Source import SourceInitError
-logger = logging.getLogger("Packages")
-
class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked,
- Bcfg2.Server.Plugin.StructFile):
+ Bcfg2.Server.Plugin.StructFile,
+ Bcfg2.Server.Plugin.Debuggable):
__identifier__ = None
-
+
def __init__(self, filename, cachepath, fam, packages, config):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
try:
Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self,
filename,
@@ -21,25 +20,45 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked,
msg = "Packages: Failed to read configuration file: %s" % err
if not os.path.exists(self.name):
msg += " Have you created it?"
- logger.error(msg)
+ self.logger.error(msg)
raise Bcfg2.Server.Plugin.PluginInitError(msg)
Bcfg2.Server.Plugin.StructFile.__init__(self, filename)
self.cachepath = cachepath
self.config = config
if not os.path.exists(self.cachepath):
# create cache directory if needed
- os.makedirs(self.cachepath)
+ try:
+ os.makedirs(self.cachepath)
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("Could not create Packages cache at %s: %s" %
+ (self.cachepath, err))
self.pkg_obj = packages
self.parsed = set()
self.loaded = False
+ def toggle_debug(self):
+ Bcfg2.Server.Plugin.Debuggable.toggle_debug(self)
+ for source in self.entries:
+ source.toggle_debug()
+
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 sorted(list(self.parsed)) == sorted(self.extras):
- logger.info("Reloading Packages plugin")
+ self.logger.info("Reloading Packages plugin")
self.pkg_obj.Reload()
self.loaded = True
@@ -56,7 +75,8 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked,
sources.xml """
stype = xsource.get("type")
if stype is None:
- logger.error("Packages: No type specified for source, skipping")
+ self.logger.error("Packages: No type specified for source, "
+ "skipping")
return None
try:
@@ -65,14 +85,14 @@ class PackagesSources(Bcfg2.Server.Plugin.SingleXMLFileBacked,
stype.title())
cls = getattr(module, "%sSource" % stype.title())
except (ImportError, AttributeError):
- logger.error("Packages: Unknown source type %s" % stype)
+ self.logger.error("Packages: Unknown source type %s" % stype)
return None
try:
source = cls(self.cachepath, xsource, self.config)
except SourceInitError:
err = sys.exc_info()[1]
- logger.error("Packages: %s" % err)
+ self.logger.error("Packages: %s" % err)
source = None
return source
diff --git a/src/lib/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
index 72c7a4bfd..a7c246940 100644
--- a/src/lib/Server/Plugins/Packages/Source.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
@@ -2,7 +2,7 @@ import os
import re
import sys
import base64
-import logging
+import Bcfg2.Server.Plugin
from Bcfg2.Bcfg2Py3k import HTTPError, HTTPBasicAuthHandler, \
HTTPPasswordMgrWithDefaultRealm, install_opener, build_opener, \
urlopen, file, cPickle
@@ -12,8 +12,6 @@ try:
except ImportError:
from md5 import md5
-logger = logging.getLogger('Packages')
-
def fetch_url(url):
if '@' in url:
mobj = re.match('(\w+://)([^:]+):([^@]+)@(.*)$', url)
@@ -32,11 +30,14 @@ class SourceInitError(Exception):
pass
-class Source(object):
- reponame_re = re.compile(r'.*/(?:RPMS\.)?([^/]+)')
+class Source(Bcfg2.Server.Plugin.Debuggable):
+ mrepo_re = re.compile(r'/RPMS\.([^/]+)')
+ pulprepo_re = re.compile(r'pulp/repos/([^/]+)')
+ genericrepo_re = re.compile(r'https?://[^/]+/(.+?)/?$')
basegroups = []
def __init__(self, basepath, xsource, config):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
self.basepath = basepath
self.xsource = xsource
self.config = config
@@ -53,8 +54,9 @@ class Source(object):
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 += "/"
@@ -111,16 +113,16 @@ class Source(object):
if os.path.exists(self.cachefile):
try:
self.load_state()
- should_read = False
+ should_read = False
except:
- logger.error("Packages: Cachefile %s load failed; "
- "falling back to file read" % self.cachefile)
+ self.logger.error("Packages: Cachefile %s load failed; "
+ "falling back to file read" % self.cachefile)
if should_read:
try:
self.read_files()
except:
- logger.error("Packages: File read failed; "
- "falling back to file download")
+ self.logger.error("Packages: File read failed; "
+ "falling back to file download")
should_download = True
if should_download or force_update:
@@ -128,23 +130,34 @@ class Source(object):
self.update()
self.read_files()
except:
- logger.error("Packages: Failed to load data for Source of %s. "
- "Some Packages will be missing."
- % self.urls)
+ self.logger.error("Packages: Failed to load data for Source "
+ "of %s. Some Packages will be missing." %
+ self.urls)
def get_repo_name(self, url_map):
# try to find a sensible name for a repo
- match = self.reponame_re.search(url_map['url'])
- if url_map['component']:
- return url_map['component']
- elif match:
- return match.group(1)
+ if url_map['components']:
+ # use the first component as the name
+ rname = url_map['components'][0]
else:
- # couldn't figure out the name from the URL or URL map
- # (which probably means its a screwy URL), so we just
- # generate a random one
- name = base64.b64encode(os.urandom(16))[:-2]
- return "%s-%s" % (self.groups[0], name)
+ name = None
+ for repo_re in (self.mrepo_re,
+ self.pulprepo_re,
+ self.genericrepo_re):
+ match = repo_re.search(url_map['url'])
+ if match:
+ break
+ if name is None:
+ # couldn't figure out the name from the URL or URL map
+ # (which probably means its a screwy URL), so we just
+ # generate a random one
+ name = base64.b64encode(os.urandom(16))[:-2]
+ rname = "%s-%s" % (self.groups[0], name)
+ # see yum/__init__.py in the yum source, lines 441-449, for
+ # the source of this regex. yum doesn't like anything but
+ # string.ascii_letters, string.digits, and [-_.:]. There
+ # doesn't seem to be a reason for this, because yum.
+ return re.sub(r'[^A-Za-z0-9-_.:]', '-', rname)
def __str__(self):
if self.rawurl:
@@ -194,18 +207,18 @@ class Source(object):
def update(self):
for url in self.urls:
- logger.info("Packages: Updating %s" % url)
+ self.logger.info("Packages: Updating %s" % url)
fname = self.escape_url(url)
try:
data = fetch_url(url)
file(fname, 'w').write(data)
except ValueError:
- logger.error("Packages: Bad url string %s" % url)
+ self.logger.error("Packages: Bad url string %s" % url)
raise
except HTTPError:
err = sys.exc_info()[1]
- logger.error("Packages: Failed to fetch url %s. HTTP response code=%s" %
- (url, err.code))
+ self.logger.error("Packages: Failed to fetch url %s. HTTP "
+ "response code=%s" % (url, err.code))
raise
def applies(self, metadata):
@@ -257,9 +270,8 @@ class Source(object):
if not found_arch:
return False
- if (self.config.has_section("global") and
- self.config.has_option("global", "magic_groups") and
- self.config.getboolean("global", "magic_groups") == False):
+ if self.config.getboolean("global", "magic_groups",
+ default=True) == False:
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 369b7a7d2..b39b6aed2 100644
--- a/src/lib/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -16,7 +16,7 @@ from Bcfg2.Server.Plugins.Packages.Collection import Collection
from Bcfg2.Server.Plugins.Packages.Source import SourceInitError, Source, \
fetch_url
-logger = logging.getLogger("Packages")
+logger = logging.getLogger(__name__)
try:
from pulp.client.consumer.config import ConsumerConfig
@@ -68,10 +68,10 @@ def _setup_pulp(config):
logger.error("Packages: Required option not found in "
"Packages/packages.conf: %s" % err)
raise Bcfg2.Server.Plugin.PluginInitError
-
+
PULPCONFIG = ConsumerConfig()
serveropts = PULPCONFIG.server
-
+
PULPSERVER = server.PulpServer(serveropts['host'],
int(serveropts['port']),
serveropts['scheme'],
@@ -85,18 +85,16 @@ 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):
- Collection.__init__(self, metadata, sources, basepath)
+
+ 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):
config = sources[0].config
- self.use_yum = has_yum
- try:
- self.use_yum &= config.getboolean("yum", "use_yum_libraries")
- except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
- self.use_yum = False
+ self.use_yum = has_yum and config.getboolean("yum",
+ "use_yum_libraries",
+ default=False)
else:
self.use_yum = False
@@ -105,7 +103,7 @@ class YumCollection(Collection):
"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)
@@ -113,11 +111,8 @@ class YumCollection(Collection):
"%s-yum.conf" % self.cachekey)
self.write_config()
- try:
- self.helper = self.config.get("yum", "helper")
- except ConfigParser.NoOptionError:
- self.helper = "/usr/sbin/bcfg2-yum-helper"
-
+ self.helper = self.config.get("yum", "helper",
+ default="/usr/sbin/bcfg2-yum-helper")
if has_pulp:
_setup_pulp(self.config)
@@ -125,10 +120,11 @@ 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"):
@@ -149,8 +145,22 @@ class YumCollection(Collection):
source.get_urls()
for url_map in source.url_map:
if url_map['arch'] in self.metadata.groups:
- reponame = source.get_repo_name(url_map)
- config.add_section(reponame)
+ basereponame = source.get_repo_name(url_map)
+ reponame = basereponame
+
+ added = False
+ while not added:
+ try:
+ config.add_section(reponame)
+ added = True
+ except ConfigParser.DuplicateSectionError:
+ match = re.match("-(\d)", reponame)
+ if match:
+ rid = int(match.group(1)) + 1
+ 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")
@@ -187,20 +197,30 @@ class YumCollection(Collection):
needkeys.add(key)
if len(needkeys):
- keypkg = lxml.etree.Element('BoundPackage', name="gpg-pubkey",
- type=self.ptype, origin='Packages')
+ if has_yum:
+ # this must be be has_yum, not use_yum, because
+ # regardless of whether the user wants to use the yum
+ # resolver we want to include gpg key data
+ keypkg = lxml.etree.Element('BoundPackage', name="gpg-pubkey",
+ type=self.ptype, origin='Packages')
+ else:
+ self.logger.warning("GPGKeys were specified for yum sources in "
+ "sources.xml, but no yum libraries were "
+ "found")
+ self.logger.warning("GPG key version/release data cannot be "
+ "determined automatically")
+ self.logger.warning("Install yum libraries, or manage GPG keys "
+ "manually")
+ keypkg = None
for key in needkeys:
# figure out the path of the key on the client
- try:
- keydir = self.config.get("global", "gpg_keypath")
- except (ConfigParser.NoOptionError,
- ConfigParser.NoSectionError):
- keydir = "/etc/pki/rpm-gpg"
+ keydir = self.config.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',
@@ -212,7 +232,8 @@ class YumCollection(Collection):
# hook to add version/release info if possible
self._add_gpg_instances(keypkg, kdata, localkey, remotekey)
independent.append(keypath)
- independent.append(keypkg)
+ if keypkg is not None:
+ independent.append(keypkg)
# see if there are any pulp sources to handle
has_pulp_sources = False
@@ -256,29 +277,36 @@ class YumCollection(Collection):
pass
except socket.error:
err = sys.exc_info()[1]
- logger.error("Packages: Could not contact Pulp server: %s" % err)
+ self.logger.error("Packages: Could not contact Pulp server: %s" %
+ err)
except:
err = sys.exc_info()[1]
- logger.error("Packages: Unknown error querying Pulp server: %s" % err)
+ self.logger.error("Packages: Unknown error querying Pulp server: %s"
+ % err)
return consumer
def _add_gpg_instances(self, keyentry, keydata, localkey, remotekey):
""" add gpg keys to the specification to ensure they get
installed """
- if self.use_yum:
- 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,
- simplefile=remotekey)
- except ValueError:
- err = sys.exc_info()[1]
- self.logger.error("Packages: Could not read GPG key %s: %s" %
- (localkey, err))
+ # this must be be has_yum, not use_yum, because regardless of
+ # whether the user wants to use the yum resolver we want to
+ # 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,
+ simplefile=remotekey)
+ except ValueError:
+ err = sys.exc_info()[1]
+ self.logger.error("Packages: Could not read GPG key %s: %s" %
+ (localkey, err))
def is_package(self, package):
if not self.use_yum:
@@ -350,7 +378,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):
@@ -364,8 +392,11 @@ class YumCollection(Collection):
# get the 'setup' variable, so we don't know how verbose
# bcfg2-server is. It'd also be nice if we could tell yum to
# log to syslog. So would a unicorn.
- cmd = [self.helper, "-c", self.cfgfile, command]
- self.logger.debug("Packages: running %s" % " ".join(cmd))
+ cmd = [self.helper, "-c", self.cfgfile]
+ if self.debug_flag:
+ cmd.append("-v")
+ cmd.append(command)
+ self.debug_log("Packages: running %s" % " ".join(cmd))
try:
helper = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
except OSError:
@@ -373,7 +404,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)
@@ -383,6 +414,9 @@ class YumCollection(Collection):
if rv:
self.logger.error("Packages: error running bcfg2-yum-helper "
"(returned %d): %s" % (rv, stderr))
+ elif self.debug_flag:
+ self.debug_log("Packages: debug info from bcfg2-yum-helper: %s" %
+ stderr)
try:
return json.loads(stdout)
except ValueError:
@@ -402,7 +436,7 @@ class YumCollection(Collection):
os.unlink(self.cfgfile)
self.write_config()
-
+
if force_update:
self.call_helper("clean")
@@ -416,24 +450,23 @@ class YumSource(Source):
self.pulp_id = None
if has_pulp and xsource.get("pulp_id"):
self.pulp_id = xsource.get("pulp_id")
-
+
_setup_pulp(self.config)
repoapi = RepositoryAPI()
try:
self.repo = repoapi.repository(self.pulp_id)
- self.gpgkeys = ["%s/%s" % (PULPCONFIG.cds['keyurl'], key)
+ self.gpgkeys = [os.path.join(PULPCONFIG.cds['keyurl'], key)
for key in repoapi.listkeys(self.pulp_id)]
except server.ServerRequestError:
err = sys.exc_info()[1]
if err[0] == 401:
msg = "Packages: Error authenticating to Pulp: %s" % err[1]
elif err[0] == 404:
- msg = "Packages: Pulp repo id %s not found: %s" % (self.pulp_id,
- err[1])
+ msg = "Packages: Pulp repo id %s not found: %s" % \
+ (self.pulp_id, err[1])
else:
- msg = "Packages: Error %d fetching pulp repo %s: %s" % (err[0],
- self.pulp_id,
- err[1])
+ msg = "Packages: Error %d fetching pulp repo %s: %s" % \
+ (err[0], self.pulp_id, err[1])
raise SourceInitError(msg)
except socket.error:
err = sys.exc_info()[1]
@@ -445,7 +478,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:
@@ -458,11 +491,8 @@ class YumSource(Source):
self.needed_paths = set()
self.file_to_arch = dict()
- self.use_yum = has_yum
- try:
- self.use_yum &= config.getboolean("yum", "use_yum_libraries")
- except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
- self.use_yum = False
+ self.use_yum = has_yum and config.getboolean("yum", "use_yum_libraries",
+ default=False)
def save_state(self):
if not self.use_yum:
@@ -470,7 +500,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:
@@ -486,7 +516,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}]
@@ -504,23 +534,23 @@ 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)
xdata = lxml.etree.XML(repomd)
except ValueError:
- logger.error("Packages: Bad url string %s" % rmdurl)
+ self.logger.error("Packages: Bad url string %s" % rmdurl)
return []
except HTTPError:
err = sys.exc_info()[1]
- logger.error("Packages: Failed to fetch url %s. code=%s" %
- (rmdurl, err.code))
+ self.logger.error("Packages: Failed to fetch url %s. code=%s" %
+ (rmdurl, err.code))
return []
except lxml.etree.XMLSyntaxError:
err = sys.exc_info()[1]
- logger.error("Packages: Failed to process metadata at %s: %s" %
- (rmdurl, err))
+ self.logger.error("Packages: Failed to process metadata at %s: %s" %
+ (rmdurl, err))
return []
urls = []
@@ -594,12 +624,13 @@ class YumSource(Source):
self.packages[arch].add(pkgname)
pdata = pkg.find(XP + 'format')
- pre = pdata.find(RP + 'requires')
self.deps[arch][pkgname] = set()
- for entry in pre.getchildren():
- self.deps[arch][pkgname].add(entry.get('name'))
- if entry.get('name').startswith('/'):
- self.needed_paths.add(entry.get('name'))
+ pre = pdata.find(RP + 'requires')
+ if pre is not None:
+ for entry in pre.getchildren():
+ self.deps[arch][pkgname].add(entry.get('name'))
+ if entry.get('name').startswith('/'):
+ self.needed_paths.add(entry.get('name'))
pro = pdata.find(RP + 'provides')
if pro != None:
for entry in pro.getchildren():
@@ -620,7 +651,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 9e1ccfa37..e4793a28d 100644
--- a/src/lib/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -4,7 +4,6 @@ import time
import copy
import glob
import shutil
-import logging
import lxml.etree
import Bcfg2.Logger
import Bcfg2.Server.Plugin
@@ -13,8 +12,6 @@ from Bcfg2.Server.Plugins.Packages import Collection
from Bcfg2.Server.Plugins.Packages.PackagesSources import PackagesSources
from Bcfg2.Server.Plugins.Packages.PackagesConfig import PackagesConfig
-logger = logging.getLogger('Packages')
-
class Packages(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.StructureValidator,
Bcfg2.Server.Plugin.Generator,
@@ -39,25 +36,40 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
os.makedirs(self.keypath)
# set up config files
- self.config = PackagesConfig(os.path.join(self.data, "packages.conf"),
- core.fam, self)
+ self.config = PackagesConfig(self)
self.sources = PackagesSources(os.path.join(self.data, "sources.xml"),
self.cachepath, core.fam, self,
self.config)
+ def toggle_debug(self):
+ Bcfg2.Server.Plugin.Plugin.toggle_debug(self)
+ self.sources.toggle_debug()
+
@property
def disableResolver(self):
try:
- return self.config.get("global", "resolver").lower() == "disabled"
+ return not self.config.getboolean("global", "resolver")
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return False
+ except ValueError:
+ # for historical reasons we also accept "enabled" and
+ # "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"
@property
def disableMetaData(self):
try:
- return self.config.get("global", "metadata").lower() == "disabled"
+ return not self.config.getboolean("global", "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"
def create_config(self, entry, metadata):
""" create yum/apt config for the specified host """
@@ -67,38 +79,41 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
'type': 'file',
'perms': '0644'}
- collection = Collection.factory(metadata, self.sources, self.data)
+ collection = self._get_collection(metadata)
entry.text = collection.get_config()
for (key, value) in list(attrib.items()):
entry.attrib.__setitem__(key, value)
def HandleEntry(self, entry, metadata):
if entry.tag == 'Package':
- collection = Collection.factory(metadata, self.sources, self.data)
+ collection = self._get_collection(metadata)
entry.set('version', 'auto')
+ entry.set('version', self.config.get("global",
+ "version",
+ default="auto"))
entry.set('type', collection.ptype)
elif entry.tag == 'Path':
- if (self.config.has_section("global") and
- ((self.config.has_option("global", "yum_config") and
- entry.get("name") == self.config.get("global",
- "yum_config")) or
- (self.config.has_option("global", "apt_config") and
- entry.get("name") == self.config.get("global",
- "apt_config")))):
+ if (entry.get("name") == self.config.get("global", "yum_config",
+ default="") or
+ entry.get("name") == self.config.get("global", "apt_config",
+ default="")):
self.create_config(entry, metadata)
def HandlesEntry(self, entry, metadata):
if entry.tag == 'Package':
- collection = Collection.factory(metadata, self.sources, self.data)
- if collection.magic_groups_match():
+ if self.config.getboolean("global", "magic_groups",
+ default=True) == True:
+ collection = self._get_collection(metadata)
+ if collection.magic_groups_match():
+ return True
+ else:
return True
elif entry.tag == 'Path':
# managed entries for yum/apt configs
- if ((self.config.has_option("global", "yum_config") and
- entry.get("name") == self.config.get("global",
- "yum_config")) or
- (self.config.has_option("global", "apt_config") and
- entry.get("name") == self.config.get("global", "apt_config"))):
+ if (entry.get("name") == self.config.get("global", "yum_config",
+ default="") or
+ entry.get("name") == self.config.get("global", "apt_config",
+ default="")):
return True
return False
@@ -109,7 +124,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
metadata - client metadata instance
structures - a list of structure-stage entry combinations
'''
- collection = Collection.factory(metadata, self.sources, self.data)
+ collection = self._get_collection(metadata)
indep = lxml.etree.Element('Independent')
self._build_packages(metadata, indep, structures,
collection=collection)
@@ -125,12 +140,14 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
return
if collection is None:
- collection = Collection.factory(metadata, self.sources, self.data)
+ collection = self._get_collection(metadata)
# initial is the set of packages that are explicitly specified
# in the configuration
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'):
@@ -152,7 +169,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)
@@ -161,13 +178,14 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
self.logger.info("Packages: Got %d unknown entries" % len(unknown))
self.logger.info("Packages: %s" % list(unknown))
newpkgs = list(packages.difference(initial))
- self.logger.debug("Packages: %d initial, %d complete, %d new" %
- (len(initial), len(packages), len(newpkgs)))
+ self.debug_log("Packages: %d initial, %d complete, %d new" %
+ (len(initial), len(packages), len(newpkgs)))
newpkgs.sort()
for pkg in newpkgs:
lxml.etree.SubElement(independent, 'BoundPackage', name=pkg,
- version='auto', type=collection.ptype,
- origin='Packages')
+ version=self.config.get("global", "version",
+ default="auto"),
+ type=collection.ptype, origin='Packages')
def Refresh(self):
'''Packages.Refresh() => True|False\nReload configuration
@@ -218,8 +236,8 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
os.unlink(cfile)
except OSError:
err = sys.exc_info()[1]
- logger.error("Packages: Could not remove cache file %s: %s"
- % (cfile, err))
+ self.logger.error("Packages: Could not remove cache file "
+ "%s: %s" % (cfile, err))
def _load_gpg_keys(self, force_update):
""" Load gpg keys from the config """
@@ -242,6 +260,10 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
if kfile not in keyfiles:
os.unlink(kfile)
+ def _get_collection(self, metadata):
+ return Collection.factory(metadata, self.sources, self.data,
+ debug=self.debug_flag)
+
def get_additional_data(self, metadata):
- collection = Collection.factory(metadata, self.sources, self.data)
+ collection = self._get_collection(metadata)
return dict(sources=collection.get_additional_data())
diff --git a/src/lib/Server/Plugins/Pkgmgr.py b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py
index bf674d0d0..e9254cdcc 100644
--- a/src/lib/Server/Plugins/Pkgmgr.py
+++ b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py
@@ -1,10 +1,13 @@
'''This module implements a package management scheme for all images'''
-__revision__ = '$Revision$'
import logging
import re
import Bcfg2.Server.Plugin
import lxml
+try:
+ set
+except NameError:
+ from sets import Set as set
logger = logging.getLogger('Bcfg2.Plugins.Pkgmgr')
@@ -63,8 +66,8 @@ class PNode(Bcfg2.Server.Plugin.INode):
if 'Package' not in pdict:
pdict['Package'] = set()
for child in data.getchildren():
- for attr in [key for key in list(data.attrib.keys())
- if key != 'name' and key not in child.attrib]:
+ attrs = set(data.attrib.keys()).difference(child.attrib.keys() + ['name'])
+ for attr in attrs:
try:
child.set(attr, data.get(attr))
except:
@@ -131,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 5ab92c011..af908eee8 100644
--- a/src/lib/Server/Plugins/Probes.py
+++ b/src/lib/Bcfg2/Server/Plugins/Probes.py
@@ -51,10 +51,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)
@@ -137,6 +137,16 @@ class ProbeSet(Bcfg2.Server.Plugin.EntrySet):
def HandleEvent(self, event):
if event.filename != self.path:
+ if (event.code2str == 'changed' and
+ event.filename.endswith("probed.xml") and
+ event.filename not in self.entries):
+ # for some reason, probed.xml is particularly prone to
+ # getting changed events before created events,
+ # because gamin is the worst ever. anyhow, we
+ # specifically handle it here to avoid a warning on
+ # every single server startup.
+ self.entry_init(event)
+ return
return self.handle_event(event)
def get_probe_data(self, metadata):
@@ -171,7 +181,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 58f7215c9..680881858 100644
--- a/src/lib/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -1,4 +1,5 @@
import os
+import re
import sys
import copy
import logging
@@ -49,6 +50,7 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
__child__ = PropertyFile
+ patterns = re.compile(r'.*\.xml$')
class Properties(Bcfg2.Server.Plugin.Plugin,
@@ -58,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)
@@ -72,4 +73,4 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
raise Bcfg2.Server.Plugin.PluginInitError
def get_additional_data(self, _):
- return copy.deepcopy(self.store.entries)
+ return copy.copy(self.store.entries)
diff --git a/src/lib/Server/Plugins/Rules.py b/src/lib/Bcfg2/Server/Plugins/Rules.py
index a146dca6a..b80ef351a 100644
--- a/src/lib/Server/Plugins/Rules.py
+++ b/src/lib/Bcfg2/Server/Plugins/Rules.py
@@ -1,15 +1,21 @@
"""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):
if entry.tag in self.Entries:
return self._matches(entry, metadata,
@@ -28,10 +34,22 @@ class Rules(Bcfg2.Server.Plugin.PrioDir):
def _matches(self, entry, metadata, rules):
if Bcfg2.Server.Plugin.PrioDir._matches(self, entry, metadata, rules):
return True
- else:
+ elif (entry.tag == "Path" and
+ ((entry.get('name').endswith("/") and
+ entry.get('name').rstrip("/") in rules) or
+ (not entry.get('name').endswith("/") and
+ entry.get('name') + '/' in rules))):
+ # special case for Path tags:
+ # http://trac.mcs.anl.gov/projects/bcfg2/ticket/967
+ return True
+ elif self._regex_enabled:
# attempt regular expression matching
for rule in rules:
- if re.match("%s$" % rule, entry.get('name')):
+ if rule not in self._regex_cache:
+ self._regex_cache[rule] = re.compile("%s$" % rule)
+ 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)
diff --git a/src/lib/Server/Plugins/SGenshi.py b/src/lib/Bcfg2/Server/Plugins/SGenshi.py
index f6a98c141..0ba08125e 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
@@ -84,7 +83,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 724d169f5..ac281ad1a 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$'
import binascii
import re
@@ -23,7 +22,7 @@ class KeyData(Bcfg2.Server.Plugin.SpecificData):
Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific,
encoding)
self.encoding = encoding
-
+
def bind_entry(self, entry, metadata):
entry.set('type', 'file')
if entry.get('encoding') == 'base64':
@@ -99,7 +98,6 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
"""
name = 'SSHbase'
- __version__ = '$Id$'
__author__ = 'bcfg-dev@mcs.anl.gov'
keypatterns = ["ssh_host_dsa_key",
@@ -148,7 +146,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,13 +204,13 @@ 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()))
@@ -230,11 +228,13 @@ 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)
if event.filename.endswith(".pub"):
+ self.logger.info("New public key %s; invalidating "
+ "ssh_known_hosts cache" % event.filename)
self.skn = False
return
@@ -244,6 +244,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
return
if event.filename.endswith('.static'):
+ self.logger.info("Static key %s %s; invalidating ssh_known_hosts "
+ "cache" % (event.filename, action))
if action == "deleted" and event.filename in self.static:
del self.static[event.filename]
self.skn = False
@@ -367,7 +369,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 7b4a08ae1..0072dc62d 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 7251ab1b5..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
@@ -98,7 +97,7 @@ class StatisticsStore(object):
newstat.set('time', asctime(localtime()))
# Add statistic
- node.append(copy.deepcopy(newstat))
+ node.append(copy.copy(newstat))
# Set dirty
self.dirty = 1
@@ -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 a127d0273..ae43388ea 100644
--- a/src/lib/Server/Plugins/Svn.py
+++ b/src/lib/Bcfg2/Server/Plugins/Svn.py
@@ -1,4 +1,5 @@
import os
+import pipes
from subprocess import Popen, PIPE
import Bcfg2.Server.Plugin
@@ -11,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 bc5e00400..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,13 +114,13 @@ class TemplateFile:
if entry.text == '':
entry.set('empty', 'true')
except TemplateError:
- terror = sys.exc_info()[1]
- logger.error('Genshi template error: %s' % terror)
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ err = sys.exc_info()[1]
+ logger.exception('Genshi template error')
+ raise Bcfg2.Server.Plugin.PluginExecutionError('Genshi template error: %s' % err)
except AttributeError:
err = sys.exc_info()[1]
- logger.error('Genshi template loading error: %s' % err)
- raise Bcfg2.Server.Plugin.PluginExecutionError
+ logger.exception('Genshi template loading error')
+ raise Bcfg2.Server.Plugin.PluginExecutionError('Genshi template loading error: %s' % err)
class TGenshi(Bcfg2.Server.Plugin.GroupSpool):
@@ -131,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 f6dd47e12..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):
@@ -26,12 +25,19 @@ class Trigger(Bcfg2.Server.Plugin.Plugin,
try:
os.stat(self.data)
except:
- self.logger.error("Trigger: spool directory %s does not exist; unloading" % self.data)
+ self.logger.error("Trigger: spool directory %s does not exist; "
+ "unloading" % self.data)
raise Bcfg2.Server.Plugin.PluginInitError
def process_statistics(self, metadata, _):
args = [metadata.hostname, '-p', metadata.profile, '-g',
':'.join([g for g in metadata.groups])]
for notifier in os.listdir(self.data):
- n = self.data + '/' + notifier
- async_run(n, args)
+ if ((notifier[-1] == '~') or
+ (notifier[:2] == '.#') or
+ (notifier[-4:] == '.swp') or
+ (notifier in ['SCCS', '.svn', '4913'])):
+ continue
+ npath = self.data + '/' + notifier
+ self.logger.debug("Running %s %s" % (npath, " ".join(args)))
+ async_run(npath, args)
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..0438ea133 100644
--- a/src/lib/Server/Reports/reports/models.py
+++ b/src/lib/Bcfg2/Server/Reports/reports/models.py
@@ -156,7 +156,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 +277,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..8c63754c9 100644
--- a/src/lib/Server/Reports/reports/sql/client.sql
+++ b/src/lib/Bcfg2/Server/Reports/reports/sql/client.sql
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 75f46c426..f541c0d2b 100644
--- a/src/lib/Server/Reports/reports/templates/base.html
+++ b/src/lib/Bcfg2/Server/Reports/reports/templates/base.html
@@ -87,7 +87,7 @@
<div style='clear:both'></div>
</div><!-- document -->
<div id="footer">
- <span>Bcfg2 Version 1.2.0</span>
+ <span>Bcfg2 Version 1.2.2</span>
</div>
<div id="calendar_div" style='position:absolute; visibility:hidden; background-color:white; layer-background-color:white;'></div>
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..0c1fae8d5 100644
--- a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html
+++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/detailed-list.html
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/Server/Reports/reports/templates/clients/index.html b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html
index e0c0d2d7a..e0c0d2d7a 100644
--- a/src/lib/Server/Reports/reports/templates/clients/index.html
+++ b/src/lib/Bcfg2/Server/Reports/reports/templates/clients/index.html
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..cadc178a7 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..8f2dec1dc 100644
--- a/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc
+++ b/src/lib/Bcfg2/Server/Reports/reports/templates/widgets/interaction_list.inc
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..f738f7bdd 100644
--- a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py
+++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/bcfg2_tags.py
@@ -81,7 +81,7 @@ def page_navigator(context):
fragment['pager'] = pager
fragment['page_limits'] = page_limits
-
+
except Resolver404:
path = "404"
except NoReverseMatch:
@@ -155,7 +155,7 @@ 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:
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..2e30125f9 100644
--- a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py
+++ b/src/lib/Bcfg2/Server/Reports/reports/templatetags/syntax_coloring.py
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 128658ff1..4d567f1a2 100644
--- a/src/lib/Server/Reports/settings.py
+++ b/src/lib/Bcfg2/Server/Reports/settings.py
@@ -6,20 +6,18 @@ from Bcfg2.Bcfg2Py3k import ConfigParser
# Django settings for bcfg2 reports project.
c = ConfigParser.ConfigParser()
if len(c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf'])) == 0:
- print("Please check that bcfg2.conf or bcfg2-web.conf exists "
- "and is readable by your web server.")
- sys.exit(1)
+ raise ImportError("Please check that bcfg2.conf or bcfg2-web.conf exists "
+ "and is readable by your web server.")
try:
- dset = c.get('statistics', 'web_debug')
+ DEBUG = c.getboolean('statistics', 'web_debug')
except:
- dset = 'false'
-
-if dset == "True":
- DEBUG = True
-else:
DEBUG = False
-
+
+if DEBUG:
+ print("Warning: Setting web_debug to True causes extraordinary memory "
+ "leaks. Only use this setting if you know what you're doing.")
+
TEMPLATE_DEBUG = DEBUG
ADMINS = (
@@ -31,8 +29,7 @@ try:
db_engine = c.get('statistics', 'database_engine')
except ConfigParser.NoSectionError:
e = sys.exc_info()[1]
- print("Failed to determine database engine: %s" % e)
- sys.exit(1)
+ raise ImportError("Failed to determine database engine: %s" % e)
db_name = ''
if c.has_option('statistics', 'database_name'):
db_name = c.get('statistics', 'database_name')
@@ -67,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:
@@ -123,12 +120,12 @@ AUTHORIZED_GROUP = ''
try:
import django.contrib.auth
except ImportError:
- print('Import of Django module failed. Is Django installed?')
+ raise ImportError('Import of Django module failed. Is Django installed?')
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..39fc10b56 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,72 @@ 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()
+ try:
+ cursor.execute('alter table %s '
+ 'drop column %s;' % (tbl, col))
+ except DatabaseError:
+ # 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 +170,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 'N/A';",
]
# this will calculate the last possible version of the database
@@ -110,7 +179,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 +191,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 +206,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 +243,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 f30c38a05..5d7973c16 100644
--- a/src/lib/Server/Snapshots/model.py
+++ b/src/lib/Bcfg2/Server/Snapshots/model.py
@@ -211,7 +211,7 @@ class File(Base, Uniquer):
type = Column(Unicode(12))
owner = Column(Unicode(12))
group = Column(Unicode(16))
- perms = Column(Integer(5))
+ perms = Column(Integer)
contents = Column(UnicodeText)
diff --git a/src/lib/Server/__init__.py b/src/lib/Bcfg2/Server/__init__.py
index 25f397565..96777b0bf 100644
--- a/src/lib/Server/__init__.py
+++ b/src/lib/Bcfg2/Server/__init__.py
@@ -1,6 +1,4 @@
-# $Id$
"""This is the set of modules for Bcfg2.Server."""
-__revision__ = '$Revision$'
__all__ = ["Admin", "Core", "FileMonitor", "Plugin", "Plugins",
"Hostbase", "Reports", "Snapshots"]
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/Plugins/Packages/PackagesConfig.py b/src/lib/Server/Plugins/Packages/PackagesConfig.py
deleted file mode 100644
index d3732bf96..000000000
--- a/src/lib/Server/Plugins/Packages/PackagesConfig.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import os
-import logging
-from Bcfg2.Bcfg2Py3k import ConfigParser
-from Bcfg2.Server.Plugins.Packages import *
-
-logger = logging.getLogger('Packages')
-
-class PackagesConfig(Bcfg2.Server.Plugin.FileBacked,
- ConfigParser.SafeConfigParser):
- def __init__(self, filename, fam, packages):
- Bcfg2.Server.Plugin.FileBacked.__init__(self, filename)
- ConfigParser.SafeConfigParser.__init__(self)
-
- self.fam = fam
- # packages.conf isn't strictly necessary, so only set a
- # monitor if it exists. if it gets added, that will require a
- # server restart
- if os.path.exists(self.name):
- self.fam.AddMonitor(self.name, self)
-
- self.pkg_obj = packages
-
- def Index(self):
- """ Build local data structures """
- for section in self.sections():
- self.remove_section(section)
- self.read(self.name)
- if self.pkg_obj.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.pkg_obj.Reload()
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2
index 58f2964f9..fb34e627b 100755
--- a/src/sbin/bcfg2
+++ b/src/sbin/bcfg2
@@ -1,7 +1,6 @@
#!/usr/bin/env python
"""Bcfg2 Client"""
-__revision__ = '$Revision$'
import fcntl
import logging
@@ -109,7 +108,7 @@ class Client:
self.logger.info(Bcfg2.Client.Tools.drivers)
raise SystemExit(0)
if self.setup['remove'] and 'services' in self.setup['remove']:
- self.logger.error("Service removal is nonsensical, disable services to get former behavior")
+ self.logger.error("Service removal is nonsensical; removed services will only be disabled")
if self.setup['remove'] not in [False,
'all',
'Services',
diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin
index 09117a3f4..d3b06733f 100755
--- a/src/sbin/bcfg2-admin
+++ b/src/sbin/bcfg2-admin
@@ -6,13 +6,12 @@ import logging
import Bcfg2.Server.Core
import Bcfg2.Logger
import Bcfg2.Options
+import Bcfg2.Server.Admin
# Compatibility import
from Bcfg2.Bcfg2Py3k import StringIO
log = logging.getLogger('bcfg2-admin')
-import Bcfg2.Server.Admin
-
def mode_import(modename):
"""Load Bcfg2.Server.Admin.<mode>."""
modname = modename.capitalize()
@@ -42,8 +41,16 @@ def main():
'configfile': Bcfg2.Options.CFILE,
'help': Bcfg2.Options.HELP,
'verbose': Bcfg2.Options.VERBOSE,
+ 'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'plugins': Bcfg2.Options.SERVER_PLUGINS,
+ 'event debug': Bcfg2.Options.DEBUG,
+ 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'encoding': Bcfg2.Options.ENCODING,
}
setup = Bcfg2.Options.OptionParser(optinfo)
+ # override default help message to include description of all modes
+ setup.hm = "%s\n%s" % (setup.buildHelpMessage(), create_description())
setup.parse(sys.argv[1:])
log_args = dict(to_syslog=False, to_console=logging.WARNING)
@@ -58,13 +65,12 @@ def main():
setup['args'] = [setup['args'][1], setup['args'][0]]
else:
# Print short help for all modes
- print("Usage:\n %s" % setup.buildHelpMessage())
- print(create_description())
+ print(setup.hm)
raise SystemExit(0)
if setup['args'][0] in get_modes():
modname = setup['args'][0].capitalize()
- if len(setup['args']) > 1 and setup['args'][1] == 'help':
+ if len(setup['args']) > 1 and setup['args'][1] == 'help':
print(mode_import(modname).__longhelp__)
raise SystemExit(0)
try:
@@ -73,7 +79,7 @@ def main():
e = sys.exc_info()[1]
log.error("Failed to load admin mode %s: %s" % (modname, e))
raise SystemExit(1)
- mode = mode_cls(setup['configfile'])
+ mode = mode_cls(setup)
mode(setup['args'][1:])
if hasattr(mode, 'bcore'):
mode.bcore.shutdown()
diff --git a/src/sbin/bcfg2-build-reports b/src/sbin/bcfg2-build-reports
index 7122fb300..7fa08110a 100755
--- a/src/sbin/bcfg2-build-reports
+++ b/src/sbin/bcfg2-build-reports
@@ -4,8 +4,6 @@
bcfg2-build-reports generates & distributes reports of statistic
information for Bcfg2."""
-__revision__ = '$Revision$'
-
import copy
import getopt
import re
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index 4412c712a..4fe4ce9d5 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -1,20 +1,22 @@
#!/usr/bin/env python
-
"""This tool loads the Bcfg2 core into an interactive debugger."""
-__revision__ = '$Revision$'
-from code import InteractiveConsole
+import os
+import sys
import cmd
import errno
import getopt
+import fnmatch
import logging
-import lxml.etree
-import os
-import sys
import tempfile
+import lxml.etree
+from code import InteractiveConsole
try:
- import profile
+ try:
+ import cProfile as profile
+ except:
+ import profile
import pstats
have_profile = True
except:
@@ -31,7 +33,8 @@ logger = logging.getLogger('bcfg2-info')
USAGE = """Commands:
build <hostname> <filename> - Build config for hostname, writing to filename
builddir <hostname> <dirname> - Build config for hostname, writing separate files to dirname
-buildall <directory> - Build configs for all clients in directory
+buildall <directory> [<hostnames*>] - Build configs for all clients in directory
+buildallfile <directory> <filename> [<hostnames*>] - Build config file for all clients in directory
buildfile <filename> <hostname> - Build config file for hostname (not written to disk)
buildbundle <bundle> <hostname> - Render a templated bundle for hostname (not written to disk)
bundles - Print out group/bundle information
@@ -42,12 +45,13 @@ event_debug - Display filesystem events as they are processed
groups - List groups
help - Print this list of available commands
mappings <type*> <name*> - Print generator mappings for optional type and name
+packageresolve <hostname> <package> [<package>...] - Resolve the specified set of packages
+packagesources <hostname> - Show package sources
profile <command> <args> - Profile a single bcfg2-info command
quit - Exit the bcfg2-info command line
showentries <hostname> <type> - Show abstract configuration entries for a given host
showclient <client1> <client2> - Show metadata for given hosts
-update - Process pending file events
-version - Print version of this tool"""
+update - Process pending file events"""
BUILDDIR_USAGE = """Usage: builddir [-f] <hostname> <output dir>
@@ -73,10 +77,12 @@ class mockLog(object):
def debug(self, *args, **kwargs):
pass
+
class dummyError(Exception):
"""This is just a dummy."""
pass
+
class FileNotBuilt(Exception):
"""Thrown when File entry contains no content."""
def __init__(self, value):
@@ -85,6 +91,30 @@ class FileNotBuilt(Exception):
def __str__(self):
return repr(self.value)
+
+def getClientList(hostglobs):
+ """ given a host glob, get a list of clients that match it """
+ # special cases to speed things up:
+ if '*' in hostglobs:
+ return list(self.metadata.clients.keys())
+ has_wildcards = False
+ for glob in hostglobs:
+ # check if any wildcard characters are in the string
+ if set('*?[]') & set(glob):
+ has_wildcards = True
+ break
+ if not has_wildcards:
+ return hostglobs
+
+ rv = set()
+ clist = set(self.metadata.clients.keys())
+ for glob in hostglobs:
+ for client in clist:
+ if fnmatch.fnmatch(client, glob):
+ rv.update(client)
+ clist.difference_update(rv)
+ return list(rv)
+
def printTabular(rows):
"""Print data in tabular format."""
cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \
@@ -103,11 +133,11 @@ def displayTrace(trace, num=80, sort=('time', 'calls')):
class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
"""Main class for bcfg2-info."""
- def __init__(self, repo, plgs, passwd, encoding, event_debug):
+ def __init__(self, repo, plgs, passwd, encoding, event_debug, filemonitor='default'):
cmd.Cmd.__init__(self)
try:
Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd,
- encoding)
+ encoding, filemonitor=filemonitor)
if event_debug:
self.fam.debug = True
except Bcfg2.Server.Core.CoreInitError:
@@ -162,8 +192,13 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
print("Dropping to python interpreter; press ^D to resume")
try:
import IPython
- shell = IPython.Shell.IPShell(argv=[], user_ns=locals())
- shell.mainloop()
+ if hasattr(IPython, "Shell"):
+ shell = IPython.Shell.IPShell(argv=[], user_ns=locals())
+ shell.mainloop()
+ elif hasattr(IPython, "embed"):
+ IPython.embed(user_ns=locals())
+ else:
+ raise ImportError
except ImportError:
sh.interact()
@@ -187,10 +222,6 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
"""Process pending filesystem events."""
self.fam.handle_events_in_interval(0.1)
- def do_version(self, _):
- """Print out code version."""
- print(__revision__)
-
def do_build(self, args):
"""Build client configuration."""
alist = args.split()
@@ -204,12 +235,9 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
if not ofile.startswith('/tmp') and not path_force:
print("Refusing to write files outside of /tmp without -f option")
return
- output = open(ofile, 'w')
- data = lxml.etree.tostring(self.BuildConfiguration(client),
+ lxml.etree.ElementTree(self.BuildConfiguration(client)).write(ofile,
encoding='UTF-8', xml_declaration=True,
pretty_print=True)
- output.write(data)
- output.close()
else:
print('Usage: build [-f] <hostname> <output file>')
@@ -250,42 +278,99 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
self.help_builddir()
def do_buildall(self, args):
- if len(args.split()) != 1:
- print("Usage: buildall <directory>")
+ if len(args.split()) < 1:
+ print("Usage: buildall <directory> [<hostnames*>]")
return
+
+ destdir = args[0]
try:
- os.mkdir(args)
- except:
- pass
- for client in self.metadata.clients:
+ os.mkdir(destdir)
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno != 17:
+ print("Could not create %s: %s" % (destdir, err))
+ if len(args) > 1:
+ clients = getClientList(args[1:])
+ else:
+ clients = list(self.metadata.clients.keys())
+ for client in clients:
self.do_build("%s %s/%s.xml" % (client, args, client))
+ def do_buildallfile(self, args):
+ """Build a config file for all clients."""
+ usage = 'Usage: buildallfile [--altsrc=<altsrc>] <directory> <filename> [<hostnames*>]'
+ try:
+ opts, args = getopt.gnu_getopt(args.split(), '', ['altsrc='])
+ except:
+ print(usage)
+ return
+ altsrc = None
+ for opt in opts:
+ if opt[0] == '--altsrc':
+ altsrc = opt[1]
+ if len(args) < 2:
+ print(usage)
+ return
+
+ destdir = args[0]
+ filename = args[1]
+ try:
+ os.mkdir(destdir)
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno != 17:
+ print("Could not create %s: %s" % (destdir, err))
+ if len(args) > 2:
+ clients = getClientList(args[1:])
+ else:
+ clients = list(self.metadata.clients.keys())
+ if altsrc:
+ args = "--altsrc %s -f %%s %%s %%s" % altsrc
+ else:
+ args = "-f %s %s %s"
+ for client in clients:
+ self.do_buildfile(args % (os.path.join(destdir, client),
+ filename, client))
+
def do_buildfile(self, args):
"""Build a config file for client."""
- usage = 'Usage: buildfile [--altsrc=<altsrc>] filename hostname'
+ usage = 'Usage: buildfile [-f <outfile>] [--altsrc=<altsrc>] filename hostname'
try:
- opts, alist = getopt.gnu_getopt(args.split(), '', ['altsrc='])
+ opts, alist = getopt.gnu_getopt(args.split(), 'f:', ['altsrc='])
except:
print(usage)
return
altsrc = None
+ outfile = None
for opt in opts:
if opt[0] == '--altsrc':
altsrc = opt[1]
- if len(alist) == 2:
- fname, client = alist
- entry = lxml.etree.Element('Path', type='file', name=fname)
- if altsrc:
- entry.set("altsrc", altsrc)
- try:
- metadata = self.build_metadata(client)
- self.Bind(entry, metadata)
- print(lxml.etree.tostring(entry, encoding="UTF-8",
- xml_declaration=True))
- except:
- print("Failed to build entry %s for host %s" % (fname, client))
- else:
+ elif opt[0] == '-f':
+ outfile = opt[1]
+ if len(alist) != 2:
print(usage)
+ return
+
+ fname, client = alist
+ entry = lxml.etree.Element('Path', type='file', name=fname)
+ if altsrc:
+ entry.set("altsrc", altsrc)
+ try:
+ metadata = self.build_metadata(client)
+ self.Bind(entry, metadata)
+ data = lxml.etree.tostring(entry, encoding="UTF-8",
+ xml_declaration=True)
+ if outfile:
+ open(outfile, 'w').write(data)
+ else:
+ print(data)
+ except IOError:
+ err = sys.exc_info()[1]
+ print("Could not write to %s: %s" % (outfile, err))
+ print(data)
+ except Exception:
+ print("Failed to build entry %s for host %s" % (fname, client))
+ raise
def do_buildbundle(self, args):
"""Render a bundle for client."""
@@ -454,7 +539,7 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
try:
meta = self.build_metadata(args)
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- print("Unable to find metadata for host %s" % client)
+ print("Unable to find metadata for host %s" % args)
return
structures = self.GetStructures(meta)
for clist in [struct.findall('Path') for struct in structures]:
@@ -473,6 +558,60 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
continue
print(cand[0].name)
+ def do_packageresolve(self, args):
+ arglist = args.split(" ")
+ if len(arglist) < 2:
+ print("Usage: packageresolve <hostname> <package> [<package>...]")
+ return
+
+ hostname = arglist[0]
+ initial = arglist[1:]
+ metadata = self.build_metadata(hostname)
+ self.plugins['Packages'].toggle_debug()
+ collection = self.plugins['Packages']._get_collection(metadata)
+ packages, unknown = collection.complete(initial)
+ newpkgs = list(packages.difference(initial))
+ print("%d initial packages" % len(initial))
+ print(" %s" % "\n ".join(initial))
+ print("%d new packages added" % len(newpkgs))
+ if newpkgs:
+ print(" %s" % "\n ".join(newpkgs))
+ print("%d unknown packages" % len(unknown))
+ if unknown:
+ print(" %s" % "\n ".join(unknown))
+
+ def do_packagesources(self, args):
+ try:
+ metadata = self.build_metadata(args)
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ print("Unable to build metadata for host %s" % args)
+ return
+ collection = self.plugins['Packages']._get_collection(metadata)
+ for source in collection.sources:
+ # get_urls() loads url_map as a side-effect
+ source.get_urls()
+ for url_map in source.url_map:
+ for arch in url_map['arches']:
+ # make sure client is in all the proper arch groups
+ if arch not in metadata.groups:
+ continue
+ reponame = source.get_repo_name(url_map)
+ print("Name: %s" % reponame)
+ print(" Type: %s" % source.ptype)
+ if url_map['url'] != '':
+ print(" URL: %s" % url_map['url'])
+ elif url_map['rawurl'] != '':
+ print(" RAWURL: %s" % url_map['rawurl'])
+ if source.gpgkeys:
+ print(" GPG Key(s): %s" % ", ".join(source.gpgkeys))
+ else:
+ print(" GPG Key(s): None")
+ if len(source.blacklist):
+ print(" Blacklist: %s" % ", ".join(source.blacklist))
+ if len(source.whitelist):
+ print(" Whitelist: %s" % ", ".join(source.whitelist))
+ print("")
+
def do_profile(self, arg):
"""."""
if not have_profile:
@@ -496,42 +635,43 @@ if __name__ == '__main__':
optinfo = {
'configfile': Bcfg2.Options.CFILE,
'help': Bcfg2.Options.HELP,
- }
- optinfo.update({
- 'event debug': Bcfg2.Options.DEBUG,
- 'profile': Bcfg2.Options.CORE_PROFILE,
- 'encoding': Bcfg2.Options.ENCODING,
- # Server options
- 'repo': Bcfg2.Options.SERVER_REPOSITORY,
- 'plugins': Bcfg2.Options.SERVER_PLUGINS,
- 'password': Bcfg2.Options.SERVER_PASSWORD,
- 'mconnect': Bcfg2.Options.SERVER_MCONNECT,
- 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
- 'location': Bcfg2.Options.SERVER_LOCATION,
- 'static': Bcfg2.Options.SERVER_STATIC,
- 'key': Bcfg2.Options.SERVER_KEY,
- 'cert': Bcfg2.Options.SERVER_CERT,
- 'ca': Bcfg2.Options.SERVER_CA,
- 'password': Bcfg2.Options.SERVER_PASSWORD,
- 'protocol': Bcfg2.Options.SERVER_PROTOCOL,
- # More options
- 'logging': Bcfg2.Options.LOGGING_FILE_PATH
- })
+ 'event debug': Bcfg2.Options.DEBUG,
+ 'profile': Bcfg2.Options.CORE_PROFILE,
+ 'encoding': Bcfg2.Options.ENCODING,
+ # Server options
+ 'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'plugins': Bcfg2.Options.SERVER_PLUGINS,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'mconnect': Bcfg2.Options.SERVER_MCONNECT,
+ 'filemonitor': Bcfg2.Options.SERVER_FILEMONITOR,
+ 'location': Bcfg2.Options.SERVER_LOCATION,
+ 'static': Bcfg2.Options.SERVER_STATIC,
+ 'key': Bcfg2.Options.SERVER_KEY,
+ 'cert': Bcfg2.Options.SERVER_CERT,
+ 'ca': Bcfg2.Options.SERVER_CA,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'protocol': Bcfg2.Options.SERVER_PROTOCOL,
+ # More options
+ 'logging': Bcfg2.Options.LOGGING_FILE_PATH
+ }
setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.hm = "Usage:\n %s\n%s" % (setup.buildHelpMessage(),
+ USAGE)
+
setup.parse(sys.argv[1:])
if setup['args'] and setup['args'][0] == 'help':
- print(USAGE)
+ print(setup.hm)
sys.exit(0)
elif setup['profile'] and have_profile:
prof = profile.Profile()
loop = prof.runcall(infoCore, setup['repo'], setup['plugins'],
setup['password'], setup['encoding'],
- setup['event debug'])
+ setup['event debug'], setup['filemonitor'])
displayTrace(prof)
else:
if setup['profile']:
print("Profiling functionality not available.")
loop = infoCore(setup['repo'], setup['plugins'], setup['password'],
- setup['encoding'], setup['event debug'])
+ setup['encoding'], setup['event debug'], setup['filemonitor'])
loop.Run(setup['args'])
diff --git a/src/sbin/bcfg2-lint b/src/sbin/bcfg2-lint
index 2d371f4aa..ad6b6139c 100755
--- a/src/sbin/bcfg2-lint
+++ b/src/sbin/bcfg2-lint
@@ -1,7 +1,6 @@
#!/usr/bin/env python
"""This tool examines your Bcfg2 specifications for errors."""
-__revision__ = '$Revision$'
import sys
import inspect
diff --git a/src/sbin/bcfg2-ping-sweep b/src/sbin/bcfg2-ping-sweep
index 70f718690..be8994be3 100755
--- a/src/sbin/bcfg2-ping-sweep
+++ b/src/sbin/bcfg2-ping-sweep
@@ -3,8 +3,6 @@
"""Generates hostinfo.xml at a regular interval."""
-__revision__ = '$Revision$'
-
from os import dup2, execl, fork, uname, wait
import sys
import time
diff --git a/src/sbin/bcfg2-reports b/src/sbin/bcfg2-reports
index 6acdd27e3..1f101b9a7 100755
--- a/src/sbin/bcfg2-reports
+++ b/src/sbin/bcfg2-reports
@@ -1,6 +1,5 @@
#!/usr/bin/env python
"""Query reporting system for client status."""
-__revision__ = '$Revision$'
import os
import sys
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index 546d5a249..89bc7c331 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -1,7 +1,6 @@
#!/usr/bin/env python
"""The XML-RPC Bcfg2 server."""
-__revision__ = '$Revision$'
import logging
import os.path
diff --git a/src/sbin/bcfg2-test b/src/sbin/bcfg2-test
index a0257ca52..01a2a4893 100644..100755
--- a/src/sbin/bcfg2-test
+++ b/src/sbin/bcfg2-test
@@ -1,41 +1,60 @@
#!/usr/bin/env python
-"""This tool verifies that all clients known to the server build without failures"""
+"""This tool verifies that all clients known to the server build
+without failures"""
import sys
+import fnmatch
+import logging
+import Bcfg2.Logger
import Bcfg2.Server.Core
from nose.core import TestProgram
+from nose.suite import LazySuite
from unittest import TestCase
class ClientTest(TestCase):
"""
- A test case representing the build of all of the configuration for a single host.
- Checks that none of the build config entities has had a failure when it is building.
- Optionally ignores some config files that we know will cause errors (because they
- are private files we don't have access to, for instance)
+ A test case representing the build of all of the configuration for
+ a single host. Checks that none of the build config entities has
+ had a failure when it is building. Optionally ignores some config
+ files that we know will cause errors (because they are private
+ files we don't have access to, for instance)
"""
__test__ = False # Do not collect
- def __init__(self, bcfg2_core, client):
+ def __init__(self, bcfg2_core, client, ignore=None):
TestCase.__init__(self)
self.bcfg2_core = bcfg2_core
self.client = client
+ if ignore is None:
+ self.ignore = dict()
+ else:
+ self.ignore = ignore
+
+ def ignore_entry(self, tag, name):
+ if tag in self.ignore:
+ if name in self.ignore[tag]:
+ return True
+ else:
+ # try wildcard matching
+ for pattern in self.ignore[tag]:
+ if fnmatch.fnmatch(name, pattern):
+ return True
+ return False
def runTest(self):
config = self.bcfg2_core.BuildConfiguration(self.client)
- failures = config.xpath('//*[@failure]')
- def format_failure(failure):
- return "%s(%s): %s" % (
- failure.tag,
- failure.attrib.get('name'),
- failure.attrib.get('failure')
- )
+ failures = []
+ msg = ["Failures:"]
+ for failure in config.xpath('//*[@failure]'):
+ if not self.ignore_entry(failure.tag, failure.get('name')):
+ failures.append(failure)
+ msg.append("%s:%s: %s" % (failure.tag, failure.get("name"),
+ failure.get("failure")))
+
+ assert len(failures) == 0, "\n".join(msg)
- assert len(failures) == 0, "Failures:\n%s" % "\n".join(
- [format_failure(failure) for failure in failures]
- )
-
def __str__(self):
return "ClientTest(%s)" % self.client
@@ -43,25 +62,55 @@ class ClientTest(TestCase):
def main():
optinfo = {
- 'configfile': Bcfg2.Options.CFILE,
- 'help': Bcfg2.Options.HELP,
- 'encoding': Bcfg2.Options.ENCODING,
- 'repo': Bcfg2.Options.SERVER_REPOSITORY,
- 'plugins': Bcfg2.Options.SERVER_PLUGINS,
- 'password': Bcfg2.Options.SERVER_PASSWORD,
- }
+ 'configfile': Bcfg2.Options.CFILE,
+ 'help': Bcfg2.Options.HELP,
+ 'encoding': Bcfg2.Options.ENCODING,
+ 'repo': Bcfg2.Options.SERVER_REPOSITORY,
+ 'plugins': Bcfg2.Options.SERVER_PLUGINS,
+ 'password': Bcfg2.Options.SERVER_PASSWORD,
+ 'verbose': Bcfg2.Options.VERBOSE,
+ 'noseopts': Bcfg2.Options.TEST_NOSEOPTS,
+ 'ignore': Bcfg2.Options.TEST_IGNORE,
+ }
setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.hm = \
+ "bcfg2-test [options] [client] [client] [...]\nOptions:\n %s" % \
+ setup.buildHelpMessage()
setup.parse(sys.argv[1:])
+
+ if setup['verbose']:
+ Bcfg2.Logger.setup_logging("bcfg2-test", to_syslog=False)
+
core = Bcfg2.Server.Core.Core(
setup['repo'],
setup['plugins'],
setup['password'],
- setup['encoding']
- )
- core.fam.handle_events_in_interval(4)
- suite = [ClientTest(core, client) for client in core.metadata.clients]
+ setup['encoding'],
+ filemonitor='pseudo'
+ )
+
+ ignore = dict()
+ for entry in setup['ignore']:
+ tag, name = entry.split(":")
+ try:
+ ignore[tag].append(name)
+ except KeyError:
+ ignore[tag] = [name]
+
+ def run_tests():
+ core.fam.handle_events_in_interval(0.1)
+
+ if setup['args']:
+ clients = setup['args']
+ else:
+ clients = core.metadata.clients
+
+ for client in clients:
+ logging.info("Building %s" % client)
+ yield ClientTest(core, client, ignore)
- TestProgram(argv=sys.argv[0:1], suite = suite)
+ TestProgram(argv=sys.argv[0:1] + setup['noseopts'],
+ suite=LazySuite(run_tests))
if __name__ == "__main__":
sys.exit(main())
diff --git a/src/sbin/bcfg2-yum-helper b/src/sbin/bcfg2-yum-helper
index 94836d748..2da7c6336 100755
--- a/src/sbin/bcfg2-yum-helper
+++ b/src/sbin/bcfg2-yum-helper
@@ -5,8 +5,6 @@ the right way to get around that in long-running processes it to have
a short-lived helper. No, seriously -- check out the yum-updatesd
code. It's pure madness. """
-__revision__ = '$Revision$'
-
import os
import sys
import yum
@@ -19,7 +17,26 @@ try:
except ImportError:
import simplejson as json
-logger = logging.getLogger('bcfg2-yum-helper')
+LOGGER = None
+
+def get_logger(verbose=0):
+ """ set up logging according to the verbose level given on the
+ command line """
+ global LOGGER
+ if LOGGER is None:
+ LOGGER = logging.getLogger(sys.argv[0])
+ stderr = logging.StreamHandler()
+ if verbose:
+ level = logging.DEBUG
+ else:
+ level = logging.WARNING
+ LOGGER.setLevel(level)
+ LOGGER.addHandler(stderr)
+ syslog = logging.handlers.SysLogHandler("/dev/log")
+ syslog.setFormatter(logging.Formatter("%(name)s: %(message)s"))
+ LOGGER.addHandler(syslog)
+ return LOGGER
+
class DepSolver(object):
def __init__(self, cfgfile, verbose=1):
@@ -28,8 +45,10 @@ class DepSolver(object):
try:
self.yumbase.preconf.debuglevel = verbose
self.yumbase.preconf.fn = cfgfile
+ self.yumbase._getConfig()
except AttributeError:
self.yumbase._getConfig(cfgfile, debuglevel=verbose)
+ self.logger = get_logger(verbose)
def get_groups(self):
try:
@@ -38,7 +57,7 @@ class DepSolver(object):
return ["noarch"]
def set_groups(self, groups):
- self._groups = set(groups).add("noarch")
+ self._groups = set(groups).union(["noarch"])
groups = property(get_groups, set_groups)
@@ -59,13 +78,13 @@ class DepSolver(object):
matches = self.yumbase.pkgSack.returnNewestByName(name=package)
except yum.Errors.PackageSackError:
if not silent:
- logger.warning("Packages: Package '%s' not found" %
- self.get_package_name(package))
+ self.logger.warning("Package '%s' not found" %
+ self.get_package_name(package))
matches = []
except yum.Errors.RepoError:
err = sys.exc_info()[1]
- logger.error("Packages: Temporary failure loading metadata for "
- "'%s': %s" % (self.get_package_name(package), err))
+ self.logger.error("Temporary failure loading metadata for %s: %s" %
+ (self.get_package_name(package), err))
matches = []
pkgs = self._filter_arch(matches)
@@ -83,8 +102,8 @@ class DepSolver(object):
deps.difference_update([dep for dep in deps
if pkg.checkPrco('provides', dep)])
else:
- logger.error("Packages: No package available: %s" %
- self.get_package_name(package))
+ self.logger.error("No package available: %s" %
+ self.get_package_name(package))
return deps
def get_provides(self, required, all=False, silent=False):
@@ -96,16 +115,15 @@ class DepSolver(object):
self.yumbase.whatProvides(*required).returnNewestByNameArch()
except yum.Errors.NoMoreMirrorsRepoError:
err = sys.exc_info()[1]
- logger.error("Packages: Temporary failure loading metadata for "
- "'%s': %s" %
- (self.get_package_name(required), err))
+ self.logger.error("Temporary failure loading metadata for %s: %s" %
+ (self.get_package_name(required), err))
return []
if prov and not all:
prov = self._filter_provides(required, prov)
elif not prov and not silent:
- logger.error("Packages: No package provides %s" %
- self.get_package_name(required))
+ self.logger.error("No package provides %s" %
+ self.get_package_name(required))
return prov
def get_group(self, group, ptype="default"):
@@ -116,11 +134,11 @@ class DepSolver(object):
if self.yumbase.comps.has_group(group):
group = self.yumbase.comps.return_group(group)
else:
- logger.warning("Packages: '%s' is not a valid group" % group)
+ self.logger.warning("%s is not a valid group" % group)
return []
except yum.Errors.GroupsError:
err = sys.exc_info()[1]
- logger.warning("Packages: %s" % err)
+ self.logger.warning(err)
return []
if ptype == "default":
@@ -134,7 +152,7 @@ class DepSolver(object):
elif ptype == "optional" or ptype == "all":
return group.packages
else:
- logger.warning("Unknown group package type '%s'" % ptype)
+ self.logger.warning("Unknown group package type '%s'" % ptype)
return []
def _filter_provides(self, package, providers):
@@ -156,14 +174,24 @@ class DepSolver(object):
# provider of perl(lib).
rv = []
for pkg in providers:
- if self.get_package_object(pkg.name) == pkg:
+ found = self.get_package_object(pkg.name)
+ if found == pkg or found.pkgtup == pkg.pkgtup:
rv.append(pkg)
+ else:
+ self.logger.debug("Skipping %s, not newest (%s)" %
+ (pkg, found))
else:
rv = providers
return [p.name for p in rv]
def _filter_arch(self, packages):
- matching = [pkg for pkg in packages if pkg.arch in self.groups]
+ matching = []
+ for pkg in packages:
+ if pkg.arch in self.groups:
+ matching.append(pkg)
+ else:
+ self.logger.debug("%s has non-matching architecture (%s)" %
+ (pkg, pkg.arch))
if matching:
return matching
else:
@@ -180,10 +208,7 @@ class DepSolver(object):
else:
return str(package)
- def complete(self, packagelist, groups=None):
- if groups is None:
- groups = []
-
+ def complete(self, packagelist):
packages = set()
pkgs = set(packagelist)
requires = set()
@@ -202,12 +227,17 @@ class DepSolver(object):
if not self.is_package(package):
# try this package out as a requirement
+ self.logger.debug("Adding requirement %s" % package)
requires.add((package, None, (None, None, None)))
continue
packages.add(package)
reqs = set(self.get_deps(package)).difference(satisfied)
if reqs:
+ self.logger.debug("Adding requirements for %s: %s" %
+ (package,
+ ",".join([self.get_package_name(r)
+ for r in reqs])))
requires.update(reqs)
reqs_satisfied = set()
@@ -222,8 +252,8 @@ class DepSolver(object):
reqs_satisfied.add(req)
continue
- logger.debug("Packages: Handling requirement '%s'" %
- self.get_package_name(req))
+ self.logger.debug("Handling requirement '%s'" %
+ self.get_package_name(req))
providers = list(set(self.get_provides(req)))
if len(providers) > 1:
# hopefully one of the providing packages is already
@@ -237,16 +267,24 @@ class DepSolver(object):
if len(best) == 1:
providers = best
elif not final_pass:
- # found no "best" package, so defer
+ self.logger.debug("%s has multiple providers: %s" %
+ (self.get_package_name(req),
+ providers))
+ self.logger.debug("No provider is obviously the "
+ "best; deferring")
providers = None
- # else: found no "best" package, but it's the
- # final pass, so include them all
+ else:
+ # found no "best" package, but it's the
+ # final pass, so include them all
+ self.logger.debug("Found multiple providers for %s,"
+ "including all" %
+ self.get_package_name(req))
if providers:
- logger.debug("Packages: Requirement '%s' satisfied by %s" %
- (self.get_package_name(req),
- ",".join([self.get_package_name(p)
- for p in providers])))
+ self.logger.debug("Requirement '%s' satisfied by %s" %
+ (self.get_package_name(req),
+ ",".join([self.get_package_name(p)
+ for p in providers])))
newpkgs = set(providers).difference(packages)
if newpkgs:
for package in newpkgs:
@@ -257,6 +295,8 @@ class DepSolver(object):
reqs_satisfied.add(req)
elif providers is not None:
# nothing provided this requirement at all
+ self.logger.debug("Nothing provides %s" %
+ self.get_package_name(req))
unknown.add(req)
reqs_satisfied.add(req)
# else, defer
@@ -283,7 +323,7 @@ class DepSolver(object):
# many files were deleted. so useful. thanks, yum.
msg = getattr(self.yumbase, "clean%s" % mdtype)()[1][0]
if not msg.startswith("0 "):
- logger.info("Packages: %s" % msg)
+ self.logger.info(msg)
def main():
@@ -291,15 +331,15 @@ def main():
parser.add_option("-c", "--config", help="Config file")
parser.add_option("-v", "--verbose", help="Verbosity level", action="count")
(options, args) = parser.parse_args()
+ logger = get_logger(options.verbose)
try:
cmd = args[0]
except IndexError:
- logger.error("bcfg2-yum-helper: No command given")
+ logger.error("No command given")
return 1
if not os.path.exists(options.config):
- logger.error("bcfg2-yum-helper: Config file %s not found" %
- options.config)
+ logger.error("Config file %s not found" % options.config)
return 1
depsolver = DepSolver(options.config, options.verbose)
@@ -308,8 +348,8 @@ def main():
print json.dumps(True)
elif cmd == "complete":
data = json.loads(sys.stdin.read())
- (packages, unknown) = depsolver.complete(data['packages'],
- groups=data['groups'])
+ depsolver.groups = data['groups']
+ (packages, unknown) = depsolver.complete(data['packages'])
print json.dumps(dict(packages=list(packages),
unknown=list(unknown)))
elif cmd == "is_virtual_package":
diff --git a/testsuite/TestFrame.py b/testsuite/TestFrame.py
deleted file mode 100644
index a76c97eec..000000000
--- a/testsuite/TestFrame.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import lxml.etree
-
-import Bcfg2.Client.Frame
-import Bcfg2.Client.Tools
-
-c1 = lxml.etree.XML("<Configuration><Bundle name='foo'><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/></Bundle></Configuration>")
-
-c2 = lxml.etree.XML("<Configuration><Bundle name='foo'><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/></Bundle></Configuration>")
-
-
-class DriverInitFail(object):
- def __init__(self, *args):
- raise Bcfg2.Client.Tools.toolInstantiationError
-
-
-class DriverInventoryFail(object):
- __name__ = 'dif'
-
- def __init__(self, logger, setup, config):
- self.config = config
- self.handled = []
- self.modified = []
- self.extra = []
-
- def Inventory(self):
- raise Error
-
-
-class TestFrame(object):
- def test__init(self):
- setup = {}
- times = {}
- config = lxml.etree.Element('Configuration')
- frame = Bcfg2.Client.Frame.Frame(config, setup, times, [], False)
- assert frame.tools == []
-
- def test__init2(self):
- setup = {}
- times = {}
- frame2 = Bcfg2.Client.Frame.Frame(c1, setup, times, ['POSIX'], False)
- assert len(frame2.tools) == 1
-
- def test__init3(self):
- setup = {}
- times = {}
- frame3 = Bcfg2.Client.Frame.Frame(c2, setup, times, ['foo'], False)
- assert len(frame3.tools) == 0
-
- def test__init4(self):
- setup = {}
- times = {}
- frame = Bcfg2.Client.Frame.Frame(c2, setup, times, [DriverInitFail], False)
- assert len(frame.tools) == 0
-
- def test__Decide_Inventory(self):
- setup = {'remove': 'none',
- 'bundle': [],
- 'interactive': False}
- times = {}
- frame = Bcfg2.Client.Frame.Frame(c2, setup, times,
- [DriverInventoryFail], False)
- assert len(frame.tools) == 1
- frame.Inventory()
- assert len([x for x in list(frame.states.values()) if x]) == 0
- frame.Decide()
- assert len(frame.whitelist)
-
- def test__Decide_Bundle(self):
- setup = {'remove': 'none',
- 'bundle': ['bar'],
- 'interactive': False}
- times = {}
- frame = Bcfg2.Client.Frame.Frame(c2, setup, times,
- [DriverInventoryFail], False)
- assert len(frame.tools) == 1
- frame.Inventory()
- assert len([x for x in list(frame.states.values()) if x]) == 0
- frame.Decide()
- assert len(frame.whitelist) == 0
-
- def test__Decide_Dryrun(self):
- setup = {'remove': 'none',
- 'bundle': [],
- 'interactive': False}
- times = {}
- frame = Bcfg2.Client.Frame.Frame(c2, setup, times,
- [DriverInventoryFail], True)
- assert len(frame.tools) == 1
- frame.Inventory()
- assert len([x for x in list(frame.states.values()) if x]) == 0
- frame.Decide()
- assert len(frame.whitelist) == 0
-
- def test__GenerateStats(self):
- setup = {'remove': 'none',
- 'bundle': [],
- 'interactive': False}
- times = {}
- frame = Bcfg2.Client.Frame.Frame(c2, setup, times,
- [DriverInventoryFail], False)
- frame.Inventory()
- frame.Decide()
- stats = frame.GenerateStats()
- assert len(stats.findall('.//Bad')[0].getchildren()) != 0
diff --git a/testsuite/TestOptions.py b/testsuite/TestOptions.py
deleted file mode 100644
index 735455d45..000000000
--- a/testsuite/TestOptions.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import os
-import sys
-
-import Bcfg2.Options
-
-
-class TestOption(object):
- def test__init(self):
- o = Bcfg2.Options.Option('foo', False, cmd='-F')
- try:
- p = Bcfg2.Options.Option('foo', False, cmd='--F')
- assert False
- except Bcfg2.Options.OptionFailure:
- pass
-
- def test_parse(self):
- o = Bcfg2.Options.Option('foo', 'test4', cmd='-F', env='TEST2',
- odesc='bar', cf=('communication', 'password'))
- o.parse([], ['-F', 'test'])
- assert o.value == 'test'
- o.parse([('-F', 'test2')], [])
- assert o.value == 'test2'
- os.environ['TEST2'] = 'test3'
- o.parse([], [])
- assert o.value == 'test3'
- del os.environ['TEST2']
- o.parse([], [])
- print(o.value)
- assert o.value == 'foobat'
- o.cf = ('communication', 'pwd')
- o.parse([], [])
- print(o.value)
- assert o.value == 'test4'
- o.cf = False
- o.parse([], [])
- assert o.value == 'test4'
-
- def test_cook(self):
- # check that default value isn't cooked
- o1 = Bcfg2.Options.Option('foo', 'test4', cook=Bcfg2.Options.bool_cook)
- o1.parse([], [])
- assert o1.value == 'test4'
- o2 = Bcfg2.Options.Option('foo', False, cmd='-F')
- o2.parse([('-F', '')], [])
- assert o2.value == True
-
-
-class TestOptionSet(object):
- def test_buildGetopt(self):
- opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
- ('bar', Bcfg2.Options.Option('foo', 'test2')),
- ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- os = Bcfg2.Options.OptionSet(opts)
- res = os.buildGetopt()
- assert 'H:' in res and 'G' in res and len(res) == 3
-
- def test_buildLongGetopt(self):
- opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
- ('bar', Bcfg2.Options.Option('foo', 'test2')),
- ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='--H',
- odesc='1', long_arg=True))]
- os = Bcfg2.Options.OptionSet(opts)
- res = os.buildLongGetopt()
- print(res)
- assert 'H=' in res and len(res) == 1
-
- def test_parse(self):
- opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
- ('bar', Bcfg2.Options.Option('foo', 'test2')),
- ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- os = Bcfg2.Options.OptionSet(opts)
- try:
- os.parse(['-G', '-H'])
- assert False
- except SystemExit:
- pass
- os2 = Bcfg2.Options.OptionSet(opts)
- try:
- os2.parse(['-h'])
- assert False
- except SystemExit:
- pass
- os3 = Bcfg2.Options.OptionSet(opts)
- os3.parse(['-G'])
- assert os3['foo'] == True
-
-
-class TestOptionParser(object):
- def test__init(self):
- opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-h')),
- ('bar', Bcfg2.Options.Option('foo', 'test2')),
- ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- os1 = Bcfg2.Options.OptionParser(opts)
- assert Bcfg2.Options.Option.cfpath == '/etc/bcfg2.conf'
- sys.argv = ['foo', '-C', '/usr/local/etc/bcfg2.conf']
- os2 = Bcfg2.Options.OptionParser(opts)
- assert Bcfg2.Options.Option.cfpath == '/usr/local/etc/bcfg2.conf'
- sys.argv = []
- os3 = Bcfg2.Options.OptionParser(opts)
- assert Bcfg2.Options.Option.cfpath == '/etc/bcfg2.conf'
diff --git a/testsuite/TestPlugin.py b/testsuite/TestPlugin.py
deleted file mode 100644
index aa619249a..000000000
--- a/testsuite/TestPlugin.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import gamin
-import lxml.etree
-import os
-
-import Bcfg2.Server.Core
-from Bcfg2.Server.Plugin import EntrySet
-
-
-class es_testtype(object):
- def __init__(self, name, properties, specific):
- self.name = name
- self.properties = properties
- self.specific = specific
- self.handled = 0
- self.built = 0
-
- def handle_event(self, event):
- self.handled += 1
-
- def bind_entry(self, entry, metadata):
- entry.set('bound', '1')
- entry.set('name', self.name)
- self.built += 1
-
-
-class metadata(object):
- def __init__(self, hostname):
- self.hostname = hostname
- self.groups = ['base', 'debian']
-
-#FIXME add test_specific
-
-
-class test_entry_set(object):
- def __init__(self):
- self.dirname = '/tmp/estest-%d' % os.getpid()
- os.path.isdir(self.dirname) or os.mkdir(self.dirname)
- self.metadata = metadata('testhost')
- self.es = EntrySet('template', self.dirname, None, es_testtype)
- self.e = Bcfg2.Server.Core.GaminEvent(1, 'template',
- gamin.GAMExists)
-
- def test_init(self):
- es = self.es
- e = self.e
- e.action = 'exists'
- es.handle_event(e)
- es.handle_event(e)
- assert len(es.entries) == 1
- assert list(es.entries.values())[0].handled == 2
- e.action = 'changed'
- es.handle_event(e)
- assert list(es.entries.values())[0].handled == 3
-
- def test_info(self):
- """Test info and info.xml handling."""
- es = self.es
- e = self.e
- dirname = self.dirname
- metadata = self.metadata
-
- # test 'info' handling
- assert es.metadata['group'] == 'root'
- self.mk_info(dirname)
- e.filename = 'info'
- e.action = 'exists'
- es.handle_event(e)
- assert es.metadata['group'] == 'sys'
- e.action = 'deleted'
- es.handle_event(e)
- assert es.metadata['group'] == 'root'
-
- # test 'info.xml' handling
- assert es.infoxml == None
- self.mk_info_xml(dirname)
- e.filename = 'info.xml'
- e.action = 'exists'
- es.handle_event(e)
- assert es.infoxml
- e.action = 'deleted'
- es.handle_event(e)
- assert es.infoxml == None
-
- def test_file_building(self):
- """Test file building."""
- self.test_init()
- ent = lxml.etree.Element('foo')
- self.es.bind_entry(ent, self.metadata)
- print(list(self.es.entries.values())[0])
- assert list(self.es.entries.values())[0].built == 1
-
- def test_host_specific_file_building(self):
- """Add a host-specific template and build it."""
- self.e.filename = 'template.H_%s' % self.metadata.hostname
- self.e.action = 'exists'
- self.es.handle_event(self.e)
- assert len(self.es.entries) == 1
- ent = lxml.etree.Element('foo')
- self.es.bind_entry(ent, self.metadata)
- # FIXME need to test that it built the _right_ file here
-
- def test_deletion(self):
- """Test deletion of files."""
- self.test_init()
- self.e.filename = 'template'
- self.e.action = 'deleted'
- self.es.handle_event(self.e)
- assert len(self.es.entries) == 0
-
- # TODO - how to clean up the temp dir & files after tests done?
-
- def mk_info(self, dir):
- i = open("%s/info" % dir, 'w')
- i.write('owner: root\n')
- i.write('group: sys\n')
- i.write('perms: 0600\n')
- i.close
-
- def mk_info_xml(self, dir):
- i = open("%s/info.xml" % dir, 'w')
- i.write('<FileInfo><Info owner="root" group="other" perms="0600" /></FileInfo>\n')
- i.close
diff --git a/testsuite/Testlib/TestOptions.py b/testsuite/Testlib/TestOptions.py
new file mode 100644
index 000000000..f5833a54a
--- /dev/null
+++ b/testsuite/Testlib/TestOptions.py
@@ -0,0 +1,124 @@
+import os
+import sys
+import unittest
+from mock import Mock, patch
+import Bcfg2.Options
+
+class TestOption(unittest.TestCase):
+ def test__init(self):
+ self.assertRaises(Bcfg2.Options.OptionFailure,
+ Bcfg2.Options.Option,
+ 'foo', False, cmd='f')
+ self.assertRaises(Bcfg2.Options.OptionFailure,
+ Bcfg2.Options.Option,
+ 'foo', False, cmd='--f')
+ self.assertRaises(Bcfg2.Options.OptionFailure,
+ Bcfg2.Options.Option,
+ 'foo', False, cmd='-foo')
+ self.assertRaises(Bcfg2.Options.OptionFailure,
+ Bcfg2.Options.Option,
+ 'foo', False, cmd='-foo', long_arg=True)
+
+ @patch('ConfigParser.ConfigParser')
+ @patch('__builtin__.open')
+ def test_getCFP(self, mock_open, mock_cp):
+ mock_cp.return_value = Mock()
+ o = Bcfg2.Options.Option('foo', False, cmd='-f')
+ self.assertFalse(o._Option__cfp)
+ o.getCFP()
+ mock_cp.assert_any_call()
+ mock_open.assert_any_call(Bcfg2.Options.DEFAULT_CONFIG_LOCATION)
+ self.assertTrue(mock_cp.return_value.readfp.called)
+
+ @patch('Bcfg2.Options.Option.cfp')
+ def test_parse(self, mock_cfp):
+ cf = ('communication', 'password')
+ o = Bcfg2.Options.Option('foo', 'test4', cmd='-F', env='TEST2',
+ odesc='bar', cf=cf)
+ o.parse([], ['-F', 'test'])
+ self.assertEqual(o.value, 'test')
+ o.parse([('-F', 'test2')], [])
+ self.assertEqual(o.value, 'test2')
+
+ os.environ['TEST2'] = 'test3'
+ o.parse([], [])
+ self.assertEqual(o.value, 'test3')
+ del os.environ['TEST2']
+
+ mock_cfp.get = Mock()
+ mock_cfp.get.return_value = 'test5'
+ o.parse([], [])
+ self.assertEqual(o.value, 'test5')
+ mock_cfp.get.assert_any_call(*cf)
+
+ o.cf = False
+ o.parse([], [])
+ assert o.value == 'test4'
+
+ def test_cook(self):
+ # check that default value isn't cooked
+ o1 = Bcfg2.Options.Option('foo', 'test4', cook=Bcfg2.Options.bool_cook)
+ o1.parse([], [])
+ assert o1.value == 'test4'
+ o2 = Bcfg2.Options.Option('foo', False, cmd='-F')
+ o2.parse([('-F', '')], [])
+ assert o2.value == True
+
+
+class TestOptionSet(unittest.TestCase):
+ def test_buildGetopt(self):
+ opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
+ ('bar', Bcfg2.Options.Option('foo', 'test2')),
+ ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
+ odesc='1'))]
+ oset = Bcfg2.Options.OptionSet(opts)
+ res = oset.buildGetopt()
+ self.assertIn('H:', res)
+ self.assertIn('G', res)
+ self.assertEqual(len(res), 3)
+
+ def test_buildLongGetopt(self):
+ opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
+ ('bar', Bcfg2.Options.Option('foo', 'test2')),
+ ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='--H',
+ odesc='1', long_arg=True))]
+ oset = Bcfg2.Options.OptionSet(opts)
+ res = oset.buildLongGetopt()
+ self.assertIn('H=', res)
+ self.assertEqual(len(res), 1)
+
+ def test_parse(self):
+ opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')),
+ ('bar', Bcfg2.Options.Option('foo', 'test2')),
+ ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
+ odesc='1'))]
+ oset = Bcfg2.Options.OptionSet(opts)
+ self.assertRaises(SystemExit,
+ oset.parse,
+ ['-G', '-H'])
+ oset2 = Bcfg2.Options.OptionSet(opts)
+ self.assertRaises(SystemExit,
+ oset2.parse,
+ ['-h'])
+ oset3 = Bcfg2.Options.OptionSet(opts)
+ oset3.parse(['-G'])
+ self.assertTrue(oset3['foo'])
+
+
+class TestOptionParser(unittest.TestCase):
+ def test__init(self):
+ opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-h')),
+ ('bar', Bcfg2.Options.Option('foo', 'test2')),
+ ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H',
+ odesc='1'))]
+ oset1 = Bcfg2.Options.OptionParser(opts)
+ self.assertEqual(Bcfg2.Options.Option.cfpath,
+ Bcfg2.Options.DEFAULT_CONFIG_LOCATION)
+ sys.argv = ['foo', '-C', '/usr/local/etc/bcfg2.conf']
+ oset2 = Bcfg2.Options.OptionParser(opts)
+ self.assertEqual(Bcfg2.Options.Option.cfpath,
+ '/usr/local/etc/bcfg2.conf')
+ sys.argv = []
+ oset3 = Bcfg2.Options.OptionParser(opts)
+ self.assertEqual(Bcfg2.Options.Option.cfpath,
+ Bcfg2.Options.DEFAULT_CONFIG_LOCATION)
diff --git a/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py
new file mode 100644
index 000000000..455731d00
--- /dev/null
+++ b/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py
@@ -0,0 +1,905 @@
+import os
+import copy
+import time
+import socket
+import unittest
+import lxml.etree
+from mock import Mock, patch
+import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugins.Metadata import *
+
+XI_NAMESPACE = "http://www.w3.org/2001/XInclude"
+XI = "{%s}" % XI_NAMESPACE
+
+clients_test_tree = lxml.etree.XML('''
+<Clients>
+ <Client name="client1" address="1.2.3.1" auth="cert"
+ location="floating" password="password2" profile="group1"/>
+ <Client name="client2" address="1.2.3.2" secure="true" profile="group2"/>
+ <Client name="client3" address="1.2.3.3" uuid="uuid1" profile="group1"
+ password="password2">
+ <Alias name="alias1"/>
+ </Client>
+ <Client name="client4" profile="group1">
+ <Alias name="alias2" address="1.2.3.2"/>
+ <Alias name="alias3"/>
+ </Client>
+ <Client name="client5" profile="group1"/>
+ <Client name="client6" profile="group1" auth="bootstrap"/>
+ <Client name="client7" profile="group1" auth="cert" address="1.2.3.4"/>
+ <Client name="client8" profile="group1" auth="cert+password"
+ address="1.2.3.5"/>
+ <Client name="client9" profile="group2" secure="true" password="password3"/>
+</Clients>''').getroottree()
+
+groups_test_tree = lxml.etree.XML('''
+<Groups xmlns:xi="http://www.w3.org/2001/XInclude">
+ <Group name="group1" default="true" profile="true" public="true"
+ category="category1"/>
+ <Group name="group2" profile="true" public="true" category="category1">
+ <Bundle name="bundle1"/>
+ <Bundle name="bundle2"/>
+ <Group name="group1"/>
+ <Group name="group4"/>
+ </Group>
+ <Group name="group3" category="category2" public="false"/>
+ <Group name="group4" category="category1">
+ <Group name="group1"/>
+ <Group name="group6"/>
+ </Group>
+ <Group name="group5"/>
+ <Group name="group7">
+ <Bundle name="bundle3"/>
+ </Group>
+ <Group name="group8">
+ <Group name="group9"/>
+ </Group>
+</Groups>''').getroottree()
+
+datastore = "/"
+
+def get_metadata_object(core=None, watch_clients=False):
+ if core is None:
+ core = Mock()
+ metadata = Metadata(core, datastore, watch_clients=watch_clients)
+ #metadata.logger = Mock()
+ return metadata
+
+
+class TestXMLMetadataConfig(unittest.TestCase):
+ def get_config_object(self, basefile="clients.xml", core=None,
+ watch_clients=False):
+ self.metadata = get_metadata_object(core=core,
+ watch_clients=watch_clients)
+ return XMLMetadataConfig(self.metadata, watch_clients, basefile)
+
+ def test_xdata(self):
+ config = self.get_config_object()
+ # we can't use assertRaises here because xdata is a property
+ try:
+ config.xdata
+ except MetadataRuntimeError:
+ pass
+ except:
+ assert False
+ config.data = "<test/>"
+ self.assertEqual(config.xdata, "<test/>")
+
+ def test_base_xdata(self):
+ config = self.get_config_object()
+ # we can't use assertRaises here because base_xdata is a property
+ try:
+ config.base_xdata
+ except MetadataRuntimeError:
+ pass
+ except:
+ assert False
+ config.basedata = "<test/>"
+ self.assertEqual(config.base_xdata, "<test/>")
+
+ def test_add_monitor(self):
+ core = Mock()
+ config = self.get_config_object(core=core)
+
+ fname = "test.xml"
+ fpath = os.path.join(self.metadata.data, "test.xml")
+
+ config.extras = []
+ config.add_monitor(fpath, fname)
+ self.assertFalse(core.fam.AddMonitor.called)
+ self.assertEqual(config.extras, [])
+
+ config = self.get_config_object(core=core, watch_clients=True)
+ config.add_monitor(fpath, fname)
+ core.fam.AddMonitor.assert_called_with(fpath, self.metadata)
+ self.assertItemsEqual(config.extras, [fname])
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.add_monitor")
+ @patch("lxml.etree.parse")
+ def test_load_xml(self, mock_parse, mock_add_monitor):
+ config = self.get_config_object("clients.xml")
+ mock_parse.side_effect = lxml.etree.XMLSyntaxError(None, None, None,
+ None)
+ config.load_xml()
+ self.assertIsNone(config.data)
+ self.assertIsNone(config.basedata)
+
+ config.data = None
+ config.basedata = None
+ mock_parse.side_effect = None
+ mock_parse.return_value.findall = Mock(return_value=[])
+ config.load_xml()
+ self.assertIsNotNone(config.data)
+ self.assertIsNotNone(config.basedata)
+
+ config.data = None
+ config.basedata = None
+
+ def side_effect(*args):
+ def second_call(*args):
+ return []
+ mock_parse.return_value.findall.side_effect = second_call
+ return [lxml.etree.Element(XI + "include", href="more.xml"),
+ lxml.etree.Element(XI + "include", href="evenmore.xml")]
+
+ mock_parse.return_value.findall = Mock(side_effect=side_effect)
+ config.load_xml()
+ mock_add_monitor.assert_any_call("more.xml")
+ mock_add_monitor.assert_any_call("evenmore.xml")
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.write_xml")
+ def test_write(self, mock_write_xml):
+ config = self.get_config_object("clients.xml")
+ config.basedata = "<test/>"
+ config.write()
+ mock_write_xml.assert_called_with(os.path.join(self.metadata.data,
+ "clients.xml"),
+ "<test/>")
+
+ @patch('Bcfg2.Server.Plugins.Metadata.locked', Mock(return_value=False))
+ @patch('fcntl.lockf', Mock())
+ @patch('__builtin__.open')
+ @patch('os.unlink')
+ @patch('os.rename')
+ @patch('os.path.islink')
+ @patch('os.readlink')
+ def test_write_xml(self, mock_readlink, mock_islink, mock_rename,
+ mock_unlink, mock_open):
+ fname = "clients.xml"
+ config = self.get_config_object(fname)
+ fpath = os.path.join(self.metadata.data, fname)
+ tmpfile = "%s.new" % fpath
+ linkdest = os.path.join(self.metadata.data, "client-link.xml")
+
+ mock_islink.return_value = False
+
+ config.write_xml(fpath, clients_test_tree)
+ mock_open.assert_called_with(tmpfile, "w")
+ self.assertTrue(mock_open.return_value.write.called)
+ mock_islink.assert_called_with(fpath)
+ mock_rename.assert_called_with(tmpfile, fpath)
+
+ mock_islink.return_value = True
+ mock_readlink.return_value = linkdest
+ config.write_xml(fpath, clients_test_tree)
+ mock_rename.assert_called_with(tmpfile, linkdest)
+
+ mock_rename.side_effect = OSError
+ self.assertRaises(MetadataRuntimeError,
+ config.write_xml, fpath, clients_test_tree)
+
+ mock_open.return_value.write.side_effect = IOError
+ self.assertRaises(MetadataRuntimeError,
+ config.write_xml, fpath, clients_test_tree)
+ mock_unlink.assert_called_with(tmpfile)
+
+ mock_open.side_effect = IOError
+ self.assertRaises(MetadataRuntimeError,
+ config.write_xml, fpath, clients_test_tree)
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch('lxml.etree.parse')
+ def test_find_xml_for_xpath(self, mock_parse):
+ config = self.get_config_object("groups.xml")
+ config.basedata = groups_test_tree
+ xpath = "//Group[@name='group1']"
+ self.assertItemsEqual(config.find_xml_for_xpath(xpath),
+ dict(filename=os.path.join(self.metadata.data,
+ "groups.xml"),
+ xmltree=groups_test_tree,
+ xquery=groups_test_tree.xpath(xpath)))
+
+ self.assertEqual(config.find_xml_for_xpath("//boguselement"), dict())
+
+ config.extras = ["foo.xml", "bar.xml", "clients.xml"]
+
+ def parse_side_effect(fname):
+ if fname == os.path.join(self.metadata.data, "clients.xml"):
+ return clients_test_tree
+ else:
+ return lxml.etree.XML("<null/>").getroottree()
+
+ mock_parse.side_effect = parse_side_effect
+ xpath = "//Client[@secure='true']"
+ self.assertItemsEqual(config.find_xml_for_xpath(xpath),
+ dict(filename=os.path.join(self.metadata.data,
+ "clients.xml"),
+ xmltree=clients_test_tree,
+ xquery=clients_test_tree.xpath(xpath)))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml")
+ def test_HandleEvent(self, mock_load_xml):
+ config = self.get_config_object("groups.xml")
+ evt = Mock()
+ evt.filename = os.path.join(self.metadata.data, "groups.xml")
+ evt.code2str = Mock(return_value="changed")
+ self.assertTrue(config.HandleEvent(evt))
+ mock_load_xml.assert_called_with()
+
+
+class TestClientMetadata(unittest.TestCase):
+ def test_inGroup(self):
+ cm = ClientMetadata("client1", "group1", ["group1", "group2"],
+ ["bundle1"], [], [], [], None, None, None)
+ self.assertTrue(cm.inGroup("group1"))
+ self.assertFalse(cm.inGroup("group3"))
+
+
+class TestMetadata(unittest.TestCase):
+ def test__init_no_fam(self):
+ # test with watch_clients=False
+ core = Mock()
+ metadata = get_metadata_object(core=core)
+ self.check_metadata_object(metadata)
+ self.assertEqual(metadata.states, dict())
+
+ def test__init_with_fam(self):
+ # test with watch_clients=True
+ core = Mock()
+ core.fam = Mock()
+ metadata = get_metadata_object(core=core, watch_clients=True)
+ self.assertEqual(len(metadata.states), 2)
+ core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
+ "groups.xml"),
+ metadata)
+ core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
+ "clients.xml"),
+ metadata)
+
+ core.fam.reset_mock()
+ core.fam.AddMonitor = Mock(side_effect=IOError)
+ self.assertRaises(Bcfg2.Server.Plugin.PluginInitError,
+ get_metadata_object,
+ core=core, watch_clients=True)
+
+ def check_metadata_object(self, metadata):
+ self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Plugin)
+ self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Metadata)
+ self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Statistics)
+ self.assertIsInstance(metadata.clients_xml, XMLMetadataConfig)
+ self.assertIsInstance(metadata.groups_xml, XMLMetadataConfig)
+ self.assertIsInstance(metadata.query, MetadataQuery)
+
+ @patch('os.makedirs', Mock())
+ @patch('__builtin__.open')
+ def test_init_repo(self, mock_open):
+ groups = "groups %s"
+ os_selection = "os"
+ clients = "clients %s"
+ Metadata.init_repo(datastore, groups, os_selection, clients)
+ mock_open.assert_any_call(os.path.join(datastore, "Metadata",
+ "groups.xml"), "w")
+ mock_open.assert_any_call(os.path.join(datastore, "Metadata",
+ "clients.xml"), "w")
+
+ @patch('lxml.etree.parse')
+ def test_get_groups(self, mock_parse):
+ metadata = get_metadata_object()
+ mock_parse.return_value = groups_test_tree
+ groups = metadata.get_groups()
+ mock_parse.assert_called_with(os.path.join(datastore, "Metadata",
+ "groups.xml"))
+ self.assertIsInstance(groups, lxml.etree._Element)
+
+ def test_search_xdata_name(self):
+ # test finding a node with the proper name
+ metadata = get_metadata_object()
+ tree = groups_test_tree
+ res = metadata._search_xdata("Group", "group1", tree)
+ self.assertIsInstance(res, lxml.etree._Element)
+ self.assertEqual(res.get("name"), "group1")
+
+ def test_search_xdata_alias(self):
+ # test finding a node with the wrong name but correct alias
+ metadata = get_metadata_object()
+ tree = clients_test_tree
+ res = metadata._search_xdata("Client", "alias3", tree, alias=True)
+ self.assertIsInstance(res, lxml.etree._Element)
+ self.assertNotEqual(res.get("name"), "alias3")
+
+ def test_search_xdata_not_found(self):
+ # test failure finding a node
+ metadata = get_metadata_object()
+ tree = clients_test_tree
+ res = metadata._search_xdata("Client", "bogus_client", tree, alias=True)
+ self.assertIsNone(res)
+
+ def search_xdata(self, tag, name, tree, alias=False):
+ metadata = get_metadata_object()
+ res = metadata._search_xdata(tag, name, tree, alias=alias)
+ self.assertIsInstance(res, lxml.etree._Element)
+ if not alias:
+ self.assertEqual(res.get("name"), name)
+
+ def test_search_group(self):
+ # test finding a group with the proper name
+ tree = groups_test_tree
+ self.search_xdata("Group", "group1", tree)
+
+ def test_search_bundle(self):
+ # test finding a bundle with the proper name
+ tree = groups_test_tree
+ self.search_xdata("Bundle", "bundle1", tree)
+
+ def test_search_client(self):
+ # test finding a client with the proper name
+ tree = clients_test_tree
+ self.search_xdata("Client", "client1", tree, alias=True)
+ self.search_xdata("Client", "alias1", tree, alias=True)
+
+ def test_add_group(self):
+ metadata = get_metadata_object()
+ metadata.groups_xml.write = Mock()
+ metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree()
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.add_group("test1", dict())
+ metadata.groups_xml.write.assert_any_call()
+ grp = metadata.search_group("test1", metadata.groups_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertEqual(grp.attrib, dict(name='test1'))
+
+ # have to call this explicitly -- usually load_xml does this
+ # on FAM events
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.add_group("test2", dict(foo='bar'))
+ metadata.groups_xml.write.assert_any_call()
+ grp = metadata.search_group("test2", metadata.groups_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertEqual(grp.attrib, dict(name='test2', foo='bar'))
+
+ # have to call this explicitly -- usually load_xml does this
+ # on FAM events
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.groups_xml.write.reset_mock()
+ self.assertRaises(MetadataConsistencyError,
+ metadata.add_group,
+ "test1", dict())
+ self.assertFalse(metadata.groups_xml.write.called)
+
+ def test_update_group(self):
+ metadata = get_metadata_object()
+ metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.data = copy.deepcopy(groups_test_tree)
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.update_group("group1", dict(foo="bar"))
+ grp = metadata.search_group("group1", metadata.groups_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertIn("foo", grp.attrib)
+ self.assertEqual(grp.get("foo"), "bar")
+ self.assertTrue(metadata.groups_xml.write_xml.called)
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.update_group,
+ "bogus_group", dict())
+
+ def test_remove_group(self):
+ metadata = get_metadata_object()
+ metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.data = copy.deepcopy(groups_test_tree)
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.remove_group("group5")
+ grp = metadata.search_group("group5", metadata.groups_xml.base_xdata)
+ self.assertIsNone(grp)
+ self.assertTrue(metadata.groups_xml.write_xml.called)
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.remove_group,
+ "bogus_group")
+
+ def test_add_bundle(self):
+ metadata = get_metadata_object()
+ metadata.groups_xml.write = Mock()
+ metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree()
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.add_bundle("bundle1")
+ metadata.groups_xml.write.assert_any_call()
+ bundle = metadata.search_bundle("bundle1",
+ metadata.groups_xml.base_xdata)
+ self.assertIsNotNone(bundle)
+ self.assertEqual(bundle.attrib, dict(name='bundle1'))
+
+ # have to call this explicitly -- usually load_xml does this
+ # on FAM events
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.groups_xml.write.reset_mock()
+ self.assertRaises(MetadataConsistencyError,
+ metadata.add_bundle,
+ "bundle1")
+ self.assertFalse(metadata.groups_xml.write.called)
+
+ def test_remove_bundle(self):
+ metadata = get_metadata_object()
+ metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.data = copy.deepcopy(groups_test_tree)
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+
+ metadata.remove_bundle("bundle1")
+ grp = metadata.search_bundle("bundle1", metadata.groups_xml.base_xdata)
+ self.assertIsNone(grp)
+ self.assertTrue(metadata.groups_xml.write_xml.called)
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.remove_bundle,
+ "bogus_bundle")
+
+ def test_add_client(self):
+ metadata = get_metadata_object()
+ metadata.clients_xml.write = Mock()
+ metadata.clients_xml.data = lxml.etree.XML('<Clients/>').getroottree()
+ metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
+
+ metadata.add_client("test1", dict())
+ metadata.clients_xml.write.assert_any_call()
+ grp = metadata.search_client("test1", metadata.clients_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertEqual(grp.attrib, dict(name='test1'))
+
+ # have to call this explicitly -- usually load_xml does this
+ # on FAM events
+ metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
+
+ metadata.add_client("test2", dict(foo='bar'))
+ metadata.clients_xml.write.assert_any_call()
+ grp = metadata.search_client("test2", metadata.clients_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertEqual(grp.attrib, dict(name='test2', foo='bar'))
+
+ # have to call this explicitly -- usually load_xml does this
+ # on FAM events
+ metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
+
+ metadata.clients_xml.write.reset_mock()
+ self.assertRaises(MetadataConsistencyError,
+ metadata.add_client,
+ "test1", dict())
+ self.assertFalse(metadata.clients_xml.write.called)
+
+ def test_update_client(self):
+ metadata = get_metadata_object()
+ metadata.clients_xml.write_xml = Mock()
+ metadata.clients_xml.data = copy.deepcopy(clients_test_tree)
+ metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
+
+ metadata.update_client("client1", dict(foo="bar"))
+ grp = metadata.search_client("client1", metadata.clients_xml.base_xdata)
+ self.assertIsNotNone(grp)
+ self.assertIn("foo", grp.attrib)
+ self.assertEqual(grp.get("foo"), "bar")
+ self.assertTrue(metadata.clients_xml.write_xml.called)
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.update_client,
+ "bogus_client", dict())
+
+ def load_clients_data(self, metadata=None, xdata=None):
+ if metadata is None:
+ metadata = get_metadata_object()
+ metadata.clients_xml.data = xdata or copy.deepcopy(clients_test_tree)
+ metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
+ evt = Mock()
+ evt.filename = os.path.join(datastore, "Metadata", "clients.xml")
+ evt.code2str = Mock(return_value="changed")
+ metadata.HandleEvent(evt)
+ return metadata
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml")
+ def test_clients_xml_event(self, mock_load_xml):
+ metadata = get_metadata_object()
+ metadata.profiles = ["group1", "group2"]
+ self.load_clients_data(metadata=metadata)
+ mock_load_xml.assert_any_call()
+ self.assertItemsEqual(metadata.clients,
+ dict([(c.get("name"), c.get("profile"))
+ for c in clients_test_tree.findall("//Client")]))
+ aliases = dict([(a.get("name"), a.getparent().get("name"))
+ for a in clients_test_tree.findall("//Alias")])
+ self.assertItemsEqual(metadata.aliases, aliases)
+
+ raliases = dict([(c.get("name"), set())
+ for c in clients_test_tree.findall("//Client")])
+ for alias in clients_test_tree.findall("//Alias"):
+ raliases[alias.getparent().get("name")].add(alias.get("name"))
+ self.assertItemsEqual(metadata.raliases, raliases)
+
+ self.assertEqual(metadata.bad_clients, dict())
+ self.assertEqual(metadata.secure,
+ [c.get("name")
+ for c in clients_test_tree.findall("//Client[@secure='true']")])
+ self.assertEqual(metadata.floating, ["client1"])
+
+ addresses = dict([(c.get("address"), [])
+ for c in clients_test_tree.findall("//*[@address]")])
+ raddresses = dict()
+ for client in clients_test_tree.findall("//Client[@address]"):
+ addresses[client.get("address")].append(client.get("name"))
+ try:
+ raddresses[client.get("name")].append(client.get("address"))
+ except KeyError:
+ raddresses[client.get("name")] = [client.get("address")]
+ for alias in clients_test_tree.findall("//Alias[@address]"):
+ addresses[alias.get("address")].append(alias.getparent().get("name"))
+ try:
+ raddresses[alias.getparent().get("name")].append(alias.get("address"))
+ except KeyError:
+ raddresses[alias.getparent().get("name")] = alias.get("address")
+
+ self.assertItemsEqual(metadata.addresses, addresses)
+ self.assertItemsEqual(metadata.raddresses, raddresses)
+ self.assertTrue(metadata.states['clients.xml'])
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_clients_xml_event_bad_clients(self):
+ metadata = get_metadata_object()
+ metadata.profiles = ["group2"]
+ self.load_clients_data(metadata=metadata)
+ clients = dict()
+ badclients = dict()
+ for client in clients_test_tree.findall("//Client"):
+ if client.get("profile") in metadata.profiles:
+ clients[client.get("name")] = client.get("profile")
+ else:
+ badclients[client.get("name")] = client.get("profile")
+ self.assertItemsEqual(metadata.clients, clients)
+ self.assertItemsEqual(metadata.bad_clients, badclients)
+
+ def load_groups_data(self, metadata=None, xdata=None):
+ if metadata is None:
+ metadata = get_metadata_object()
+ metadata.groups_xml.data = xdata or copy.deepcopy(groups_test_tree)
+ metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
+ evt = Mock()
+ evt.filename = os.path.join(datastore, "Metadata", "groups.xml")
+ evt.code2str = Mock(return_value="changed")
+ metadata.HandleEvent(evt)
+ return metadata
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml")
+ def test_groups_xml_event(self, mock_load_xml):
+ dup_data = copy.deepcopy(groups_test_tree)
+ lxml.etree.SubElement(dup_data.getroot(),
+ "Group", name="group1")
+ metadata = self.load_groups_data(xdata=dup_data)
+ mock_load_xml.assert_any_call()
+ self.assertEqual(metadata.public, ["group1", "group2"])
+ self.assertEqual(metadata.private, ["group3"])
+ self.assertEqual(metadata.profiles, ["group1", "group2"])
+ self.assertItemsEqual(metadata.groups.keys(),
+ [g.get("name")
+ for g in groups_test_tree.findall("/Group")])
+ self.assertEqual(metadata.categories,
+ dict(group1="category1",
+ group2="category1",
+ group3="category2",
+ group4="category1"))
+ self.assertEqual(metadata.default, "group1")
+ self.assertTrue(metadata.states['groups.xml'])
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.add_client")
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.update_client")
+ def test_set_profile(self, mock_update_client, mock_add_client):
+ metadata = get_metadata_object()
+ metadata.states['clients.xml'] = False
+ self.assertRaises(MetadataRuntimeError,
+ metadata.set_profile,
+ None, None, None)
+
+ self.load_groups_data(metadata=metadata)
+ self.load_clients_data(metadata=metadata)
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.set_profile,
+ "client1", "group5", None)
+
+ metadata.clients_xml.write = Mock()
+ metadata.set_profile("client1", "group2", None)
+ mock_update_client.assert_called_with("client1", dict(profile="group2"))
+ metadata.clients_xml.write.assert_any_call()
+ self.assertEqual(metadata.clients["client1"], "group2")
+
+ metadata.clients_xml.write.reset_mock()
+ metadata.set_profile("client_new", "group1", None)
+ mock_add_client.assert_called_with("client_new", dict(profile="group1"))
+ metadata.clients_xml.write.assert_any_call()
+ self.assertEqual(metadata.clients["client_new"], "group1")
+
+ metadata.session_cache[('1.2.3.6', None)] = (None, 'client_new2')
+ metadata.clients_xml.write.reset_mock()
+ metadata.set_profile("uuid_new", "group1", ('1.2.3.6', None))
+ mock_add_client.assert_called_with("client_new2",
+ dict(uuid='uuid_new',
+ profile="group1",
+ address='1.2.3.6'))
+ metadata.clients_xml.write.assert_any_call()
+ self.assertEqual(metadata.clients["uuid_new"], "group1")
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("socket.gethostbyaddr")
+ def test_resolve_client(self, mock_gethostbyaddr):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ metadata.session_cache[('1.2.3.3', None)] = (time.time(), 'client3')
+ self.assertEqual(metadata.resolve_client(('1.2.3.3', None)), 'client3')
+
+ self.assertRaises(MetadataConsistencyError,
+ metadata.resolve_client,
+ ('1.2.3.2', None))
+ self.assertEqual(metadata.resolve_client(('1.2.3.1', None)), 'client1')
+
+ metadata.session_cache[('1.2.3.3', None)] = (time.time() - 100,
+ 'client3')
+ self.assertEqual(metadata.resolve_client(('1.2.3.3', None)), 'client3')
+ self.assertEqual(metadata.resolve_client(('1.2.3.3', None),
+ cleanup_cache=True), 'client3')
+ self.assertEqual(metadata.session_cache, dict())
+
+ mock_gethostbyaddr.return_value = ('client6', [], ['1.2.3.6'])
+ self.assertEqual(metadata.resolve_client(('1.2.3.6', None)), 'client6')
+ mock_gethostbyaddr.assert_called_with('1.2.3.6')
+
+ mock_gethostbyaddr.reset_mock()
+ mock_gethostbyaddr.return_value = ('alias3', [], ['1.2.3.7'])
+ self.assertEqual(metadata.resolve_client(('1.2.3.7', None)), 'client4')
+ mock_gethostbyaddr.assert_called_with('1.2.3.7')
+
+ mock_gethostbyaddr.reset_mock()
+ mock_gethostbyaddr.return_value = None
+ mock_gethostbyaddr.side_effect = socket.herror
+ self.assertRaises(MetadataConsistencyError,
+ metadata.resolve_client,
+ ('1.2.3.8', None))
+ mock_gethostbyaddr.assert_called_with('1.2.3.8')
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.write_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.ClientMetadata")
+ def test_get_initial_metadata(self, mock_clientmetadata):
+ metadata = get_metadata_object()
+ metadata.states['clients.xml'] = False
+ self.assertRaises(MetadataRuntimeError,
+ metadata.get_initial_metadata, None)
+
+ self.load_groups_data(metadata=metadata)
+ self.load_clients_data(metadata=metadata)
+
+ metadata.get_initial_metadata("client1")
+ self.assertEqual(mock_clientmetadata.call_args[0][:9],
+ ("client1", "group1", set(["group1"]), set(), set(),
+ set(["1.2.3.1"]), dict(), None, 'password2'))
+
+ metadata.get_initial_metadata("client2")
+ self.assertEqual(mock_clientmetadata.call_args[0][:9],
+ ("client2", "group2", set(["group1", "group2"]),
+ set(["bundle1", "bundle2"]), set(),
+ set(["1.2.3.2"]), dict(category1="group1"),
+ None, None))
+
+ imd = metadata.get_initial_metadata("alias1")
+ self.assertEqual(mock_clientmetadata.call_args[0][:9],
+ ("client3", "group1", set(["group1"]), set(),
+ set(['alias1']), set(["1.2.3.3"]), dict(), 'uuid1',
+ 'password2'))
+
+ imd = metadata.get_initial_metadata("client_new")
+ self.assertEqual(mock_clientmetadata.call_args[0][:9],
+ ("client_new", "group1", set(["group1"]), set(),
+ set(), set(), dict(), None, None))
+
+ metadata.default = None
+ self.assertRaises(MetadataConsistencyError,
+ metadata.get_initial_metadata,
+ "client_new2")
+
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_get_all_group_names(self):
+ metadata = self.load_groups_data()
+ self.assertItemsEqual(metadata.get_all_group_names(),
+ set([g.get("name")
+ for g in groups_test_tree.findall("//Group")]))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_get_all_groups_in_category(self):
+ metadata = self.load_groups_data()
+ self.assertItemsEqual(metadata.get_all_groups_in_category("category1"),
+ set([g.get("name")
+ for g in groups_test_tree.findall("//Group[@category='category1']")]))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_get_client_names_by_profiles(self):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ self.assertItemsEqual(metadata.get_client_names_by_profiles("group2"),
+ [c.get("name")
+ for c in clients_test_tree.findall("//Client[@profile='group2']")])
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_get_client_names_by_groups(self):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ # this is not the best test in the world, since we mock
+ # core.build_metadata to just build _initial_ metadata, which
+ # is not at all the same thing. it turns out that mocking
+ # this out without starting a Bcfg2 server is pretty
+ # non-trivial, so this works-ish
+ metadata.core.build_metadata = Mock()
+ metadata.core.build_metadata.side_effect = \
+ lambda c: metadata.get_initial_metadata(c)
+ self.assertItemsEqual(metadata.get_client_names_by_groups(["group2"]),
+ [c.get("name")
+ for c in clients_test_tree.findall("//Client[@profile='group2']")])
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_merge_additional_groups(self):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ imd = metadata.get_initial_metadata("client2")
+
+ # test adding a group excluded by categories
+ oldgroups = imd.groups
+ metadata.merge_additional_groups(imd, ["group4"])
+ self.assertEqual(imd.groups, oldgroups)
+
+ # test adding a private group
+ oldgroups = imd.groups
+ metadata.merge_additional_groups(imd, ["group3"])
+ self.assertEqual(imd.groups, oldgroups)
+
+ # test adding groups with bundles
+ oldgroups = imd.groups
+ oldbundles = imd.bundles
+ metadata.merge_additional_groups(imd, ["group7"])
+ self.assertEqual(imd.groups, oldgroups.union(["group7"]))
+ self.assertEqual(imd.bundles, oldbundles.union(["bundle3"]))
+
+ # test adding multiple groups
+ imd = metadata.get_initial_metadata("client2")
+ oldgroups = imd.groups
+ metadata.merge_additional_groups(imd, ["group6", "group8"])
+ self.assertItemsEqual(imd.groups,
+ oldgroups.union(["group6", "group8", "group9"]))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ def test_merge_additional_data(self):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ imd = metadata.get_initial_metadata("client1")
+
+ # we need to use a unique attribute name for this test. this
+ # is probably overkill, but it works
+ pattern = "connector%d"
+ for i in range(0, 100):
+ connector = pattern % i
+ if not hasattr(imd, connector):
+ break
+ self.assertFalse(hasattr(imd, connector),
+ "Could not find unique connector name to test "
+ "merge_additional_data()")
+
+ metadata.merge_additional_data(imd, connector, "test data")
+ self.assertEqual(getattr(imd, connector), "test data")
+ self.assertIn(connector, imd.connectors)
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.resolve_client")
+ def test_validate_client_address(self, mock_resolve_client):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ self.assertTrue(metadata.validate_client_address("client1",
+ (None, None)))
+ self.assertTrue(metadata.validate_client_address("client2",
+ ("1.2.3.2", None)))
+ self.assertFalse(metadata.validate_client_address("client2",
+ ("1.2.3.8", None)))
+ self.assertTrue(metadata.validate_client_address("client4",
+ ("1.2.3.2", None)))
+ # this is upper case to ensure that case is folded properly in
+ # validate_client_address()
+ mock_resolve_client.return_value = "CLIENT4"
+ self.assertTrue(metadata.validate_client_address("client4",
+ ("1.2.3.7", None)))
+ mock_resolve_client.assert_called_with(("1.2.3.7", None))
+
+ mock_resolve_client.reset_mock()
+ self.assertFalse(metadata.validate_client_address("client5",
+ ("1.2.3.5", None)))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.validate_client_address")
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.resolve_client")
+ def test_AuthenticateConnection(self, mock_resolve_client,
+ mock_validate_client_address):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ metadata.password = "password1"
+
+ cert = dict(subject=[[("commonName", "client1")]])
+ mock_validate_client_address.return_value = False
+ self.assertFalse(metadata.AuthenticateConnection(cert, "root", None,
+ "1.2.3.1"))
+ mock_validate_client_address.return_value = True
+ self.assertTrue(metadata.AuthenticateConnection(cert, "root", None,
+ "1.2.3.1"))
+ # floating cert-auth clients add themselves to the cache
+ self.assertIn("1.2.3.1", metadata.session_cache)
+ self.assertEqual(metadata.session_cache["1.2.3.1"][1], "client1")
+
+ cert = dict(subject=[[("commonName", "client7")]])
+ self.assertTrue(metadata.AuthenticateConnection(cert, "root", None,
+ "1.2.3.4"))
+ # non-floating cert-auth clients do not add themselves to the cache
+ self.assertNotIn("1.2.3.4", metadata.session_cache)
+
+ cert = dict(subject=[[("commonName", "client8")]])
+
+ mock_resolve_client.return_value = "client5"
+ self.assertTrue(metadata.AuthenticateConnection(None, "root",
+ "password1", "1.2.3.8"))
+
+ mock_resolve_client.side_effect = MetadataConsistencyError
+ self.assertFalse(metadata.AuthenticateConnection(None, "root",
+ "password1",
+ "1.2.3.8"))
+
+ # secure mode, no password
+ self.assertFalse(metadata.AuthenticateConnection(None, 'client2', None,
+ "1.2.3.2"))
+
+ self.assertTrue(metadata.AuthenticateConnection(None, 'uuid1',
+ "password1", "1.2.3.3"))
+ # non-root, non-cert clients populate session cache
+ self.assertIn("1.2.3.3", metadata.session_cache)
+ self.assertEqual(metadata.session_cache["1.2.3.3"][1], "client3")
+
+ # use alternate password
+ self.assertTrue(metadata.AuthenticateConnection(None, 'client3',
+ "password2", "1.2.3.3"))
+
+ # test secure mode
+ self.assertFalse(metadata.AuthenticateConnection(None, 'client9',
+ "password1",
+ "1.2.3.9"))
+ self.assertTrue(metadata.AuthenticateConnection(None, 'client9',
+ "password3", "1.2.3.9"))
+
+ self.assertFalse(metadata.AuthenticateConnection(None, "client5",
+ "password2",
+ "1.2.3.7"))
+
+ @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock())
+ @patch("Bcfg2.Server.Plugins.Metadata.Metadata.update_client")
+ def test_process_statistics(self, mock_update_client):
+ metadata = self.load_clients_data(metadata=self.load_groups_data())
+ md = Mock()
+ md.hostname = "client6"
+ metadata.process_statistics(md, None)
+ mock_update_client.assert_called_with(md.hostname,
+ dict(auth='cert'))
+
+ mock_update_client.reset_mock()
+ md.hostname = "client5"
+ metadata.process_statistics(md, None)
+ self.assertFalse(mock_update_client.called)
+
+ def test_viz(self):
+ pass
diff --git a/tools/bcfg2-export-config b/tools/bcfg2-export-config
index 4e7790a22..f7e939a3e 100755
--- a/tools/bcfg2-export-config
+++ b/tools/bcfg2-export-config
@@ -16,7 +16,6 @@ The metadata information get stored in an index file in the output directory.
The format of the index file is:
user,group,permission,/path/to/file
'''
-__revision__ = '$Revision: 221 $'
import logging, lxml.etree, Bcfg2.Logging, Bcfg2.Server.Core
import Bcfg2.Server.Plugins.Metadata, Bcfg2.Server.Plugin
diff --git a/tools/create-debian-pkglist.py b/tools/create-debian-pkglist.py
index 8e1210582..4b268c6ee 100755
--- a/tools/create-debian-pkglist.py
+++ b/tools/create-debian-pkglist.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python
'''Build debian/ubuntu package indexes'''
-__revision__ = '$Id$'
# Original code from Bcfg2 sources
diff --git a/tools/encap-util-count.sh b/tools/encap-util-count.sh
index 848a8d648..460bb689b 100755
--- a/tools/encap-util-count.sh
+++ b/tools/encap-util-count.sh
@@ -1,5 +1,4 @@
#!/bin/sh
-# $Id$
# This shows a count of encap packages per directory
# Can be useful to make sure you have everything
diff --git a/tools/encap-util-expand.sh b/tools/encap-util-expand.sh
index 2bb3f7f62..5764bdc79 100755
--- a/tools/encap-util-expand.sh
+++ b/tools/encap-util-expand.sh
@@ -1,5 +1,4 @@
#!/bin/sh
-# $Id$
# This gets the encaps out of a makeself .run file
diff --git a/tools/encap-util-place.sh b/tools/encap-util-place.sh
index c3e96ebd8..dedf14448 100755
--- a/tools/encap-util-place.sh
+++ b/tools/encap-util-place.sh
@@ -1,5 +1,4 @@
#!/bin/bash
-# $Id$
# This puts encaps in the right directories, creating the
# directories if needed.
diff --git a/tools/encap-util-xml.sh b/tools/encap-util-xml.sh
index 0fdd75fe4..ae762637f 100755
--- a/tools/encap-util-xml.sh
+++ b/tools/encap-util-xml.sh
@@ -1,5 +1,4 @@
#!/bin/sh
-# $Id$
# This builds the XML Pkgmgr files for the encap directory
# structure created by the place script. It assumes the
diff --git a/tools/hostbasepush.py b/tools/hostbasepush.py
index 070711c82..02b7a582f 100755
--- a/tools/hostbasepush.py
+++ b/tools/hostbasepush.py
@@ -1,7 +1,5 @@
#!/usr/bin/python
-__revision__ = "$Revision: $"
-
import os
import Bcfg2.Client.Proxy
diff --git a/tools/pkgmgr_gen.py b/tools/pkgmgr_gen.py
index 03d36dfc0..7101ba17d 100755
--- a/tools/pkgmgr_gen.py
+++ b/tools/pkgmgr_gen.py
@@ -10,7 +10,6 @@
bcfg2 client drivers. The output can also contain the PackageList
and nested group headers.
"""
-__revision__ = '$Revision: $'
import collections
import datetime
import glob