summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml76
-rw-r--r--README.rst (renamed from README)4
-rw-r--r--debian/bcfg2-server.install1
-rw-r--r--debian/bcfg2-utils.install1
-rw-r--r--debian/bcfg2.install1
-rw-r--r--debian/changelog18
-rw-r--r--debian/control18
-rwxr-xr-xdebian/rules4
-rw-r--r--doc/appendix/guides/authentication.txt2
-rw-r--r--doc/appendix/guides/import-existing-ssh-keys.txt7
-rw-r--r--doc/appendix/guides/ubuntu.txt2
-rw-r--r--doc/client/tools/vcs.txt28
-rw-r--r--doc/conf.py2
-rw-r--r--doc/development/caching.txt3
-rw-r--r--doc/development/compat.txt12
-rw-r--r--doc/development/plugins.txt10
-rw-r--r--doc/exts/xmlschema.py3
-rw-r--r--doc/installation/distributions.txt10
-rw-r--r--doc/installation/prerequisites.txt5
-rw-r--r--doc/introduction/os-support.txt4
-rw-r--r--doc/man/bcfg2.conf.txt9
-rw-r--r--doc/man/bcfg2.txt2
-rw-r--r--doc/releases/1.3.6.txt11
-rw-r--r--doc/releases/1.4.0pre2.txt17
-rw-r--r--doc/releases/index.txt2
-rw-r--r--doc/server/encryption.txt9
-rw-r--r--doc/server/plugins/connectors/grouplogic.txt4
-rw-r--r--doc/server/plugins/connectors/templatehelper.txt6
-rw-r--r--doc/server/plugins/generators/cfg.txt8
-rw-r--r--doc/server/plugins/generators/nagiosgen.txt6
-rw-r--r--doc/server/plugins/generators/packages.txt96
-rw-r--r--doc/server/plugins/generators/rules.txt25
-rw-r--r--doc/server/plugins/generators/sshbase.txt31
-rw-r--r--doc/server/plugins/grouping/ldap.txt227
-rw-r--r--doc/server/plugins/grouping/metadata.txt3
-rw-r--r--doc/server/plugins/structures/bundler/index.txt2
-rw-r--r--doc/server/plugins/structures/defaults.txt7
-rw-r--r--doc/unsorted/vim_snippet.txt2
-rw-r--r--man/bcfg2.12
-rw-r--r--man/bcfg2.conf.59
-rw-r--r--misc/bcfg2-selinux.spec2
-rw-r--r--misc/bcfg2.spec25
-rw-r--r--osx/Introduction.txt2
-rw-r--r--osx/Makefile2
-rw-r--r--osx/macports/Portfile2
-rw-r--r--reports/reports.wsgi10
-rw-r--r--schemas/clients.xsd2
-rw-r--r--schemas/packages.xsd22
-rw-r--r--schemas/pkgtype.xsd7
-rw-r--r--schemas/pkgvars.xsd43
-rw-r--r--schemas/types.xsd1
-rwxr-xr-xsetup.py6
-rw-r--r--solaris-ips/MANIFEST.bcfg2-server.header2
-rw-r--r--solaris-ips/MANIFEST.bcfg2.header2
-rw-r--r--solaris-ips/Makefile2
-rw-r--r--solaris-ips/pkginfo.bcfg22
-rw-r--r--solaris-ips/pkginfo.bcfg2-server2
-rw-r--r--solaris/Makefile2
-rw-r--r--solaris/pkginfo.bcfg22
-rw-r--r--solaris/pkginfo.bcfg2-server2
-rw-r--r--src/lib/Bcfg2/Client/Proxy.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/APT.py56
-rw-r--r--src/lib/Bcfg2/Client/Tools/BundleDeps.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/Chkconfig.py8
-rw-r--r--src/lib/Bcfg2/Client/Tools/DebInit.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/Dummy.py16
-rw-r--r--src/lib/Bcfg2/Client/Tools/FreeBSDInit.py1
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py38
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Device.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/File.py20
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/__init__.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py69
-rw-r--r--src/lib/Bcfg2/Client/Tools/Pacman.py11
-rw-r--r--src/lib/Bcfg2/Client/Tools/Pkgng.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/RPM.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/RcUpdate.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/SYSV.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/Systemd.py22
-rw-r--r--src/lib/Bcfg2/Client/XML.py2
-rw-r--r--src/lib/Bcfg2/Client/__init__.py19
-rw-r--r--src/lib/Bcfg2/Compat.py6
-rw-r--r--src/lib/Bcfg2/DBSettings.py152
-rw-r--r--src/lib/Bcfg2/Logger.py9
-rw-r--r--src/lib/Bcfg2/Options/Options.py2
-rw-r--r--src/lib/Bcfg2/Options/Parser.py11
-rw-r--r--src/lib/Bcfg2/Options/Subcommands.py38
-rw-r--r--src/lib/Bcfg2/Options/Types.py27
-rw-r--r--src/lib/Bcfg2/Reporting/Collector.py2
-rw-r--r--src/lib/Bcfg2/Reporting/Compat.py14
-rwxr-xr-xsrc/lib/Bcfg2/Reporting/Reports.py198
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py74
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0001_initial.py744
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0002_convert_perms_to_mode.py181
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0003_expand_hash_key.py222
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0004_profile_can_be_null.py164
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py705
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py396
-rw-r--r--src/lib/Bcfg2/Reporting/migrations/0007_add_flag_fields_interaction.py26
-rw-r--r--src/lib/Bcfg2/Reporting/models.py21
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0001_initial.py465
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0002_convert_perms_to_mode.py170
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0003_expand_hash_key.py180
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0004_profile_can_be_null.py156
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0005_add_selinux_entry_support.py485
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0006_add_user_group_entry_support.py340
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/0007_add_flag_fields_interaction.py298
-rw-r--r--src/lib/Bcfg2/Reporting/south_migrations/__init__.py0
-rw-r--r--src/lib/Bcfg2/Reporting/templates/base-timeview.html2
-rw-r--r--src/lib/Bcfg2/Reporting/templates/base.html8
-rw-r--r--src/lib/Bcfg2/Reporting/templates/clients/detail.html19
-rw-r--r--src/lib/Bcfg2/Reporting/templates/clients/detailed-list.html13
-rw-r--r--src/lib/Bcfg2/Reporting/templates/clients/index.html5
-rw-r--r--src/lib/Bcfg2/Reporting/templates/clients/manage.html7
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/common.html4
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/entry_status.html11
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/item.html8
-rw-r--r--src/lib/Bcfg2/Reporting/templates/config_items/listing.html7
-rw-r--r--src/lib/Bcfg2/Reporting/templates/displays/summary.html7
-rw-r--r--src/lib/Bcfg2/Reporting/templates/displays/timing.html7
-rw-r--r--src/lib/Bcfg2/Reporting/templates/widgets/interaction_list.inc6
-rw-r--r--src/lib/Bcfg2/Reporting/templatetags/bcfg2_compat.py14
-rw-r--r--src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py11
-rw-r--r--src/lib/Bcfg2/Reporting/urls.py56
-rw-r--r--src/lib/Bcfg2/Reporting/views.py74
-rw-r--r--src/lib/Bcfg2/Server/Admin.py48
-rw-r--r--src/lib/Bcfg2/Server/BuiltinCore.py21
-rw-r--r--src/lib/Bcfg2/Server/Cache.py17
-rw-r--r--src/lib/Bcfg2/Server/CherrypyCore.py16
-rw-r--r--src/lib/Bcfg2/Server/Core.py53
-rwxr-xr-xsrc/lib/Bcfg2/Server/Encryption.py2
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py1
-rw-r--r--src/lib/Bcfg2/Server/Info.py97
-rw-r--r--src/lib/Bcfg2/Server/Lint/Bundler.py4
-rw-r--r--src/lib/Bcfg2/Server/Lint/MergeFiles.py3
-rw-r--r--src/lib/Bcfg2/Server/Lint/RequiredAttrs.py166
-rw-r--r--src/lib/Bcfg2/Server/Lint/TemplateHelper.py9
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py3
-rw-r--r--src/lib/Bcfg2/Server/Lint/__init__.py1
-rw-r--r--src/lib/Bcfg2/Server/MultiprocessingCore.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugin/__init__.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugin/helpers.py100
-rw-r--r--src/lib/Bcfg2/Server/Plugins/AWSTags.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Bundler.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py39
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Defaults.py12
-rw-r--r--src/lib/Bcfg2/Server/Plugins/GroupLogic.py13
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Ldap.py413
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py24
-rw-r--r--src/lib/Bcfg2/Server/Plugins/NagiosGen.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Ohai.py7
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Apt.py22
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Collection.py27
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Dummy.py35
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Layman.py142
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Pac.py145
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Portage.py333
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Source.py134
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py157
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py47
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py75
-rw-r--r--src/lib/Bcfg2/Server/Plugins/PkgVars.py65
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Probes.py31
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py41
-rw-r--r--src/lib/Bcfg2/Server/Plugins/PuppetENC.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Reporting.py15
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Rules.py27
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSHbase.py35
-rw-r--r--src/lib/Bcfg2/Server/Plugins/TemplateHelper.py26
-rw-r--r--src/lib/Bcfg2/Server/Reports/reports/models.py2
-rw-r--r--src/lib/Bcfg2/Server/Reports/updatefix.py8
-rw-r--r--src/lib/Bcfg2/Server/migrations/0001_initial.py45
-rw-r--r--src/lib/Bcfg2/Server/migrations/__init__.py0
-rw-r--r--src/lib/Bcfg2/Server/models.py29
-rw-r--r--src/lib/Bcfg2/Server/south_migrations/0001_initial.py70
-rw-r--r--src/lib/Bcfg2/Server/south_migrations/__init__.py0
-rw-r--r--src/lib/Bcfg2/Utils.py27
-rwxr-xr-xsrc/lib/Bcfg2/manage.py33
-rw-r--r--src/lib/Bcfg2/version.py2
-rwxr-xr-xsrc/sbin/bcfg2-info1
-rw-r--r--testsuite/Testschema/test_schema.py11
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py28
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py54
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py8
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions/TestComponents.py10
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions/TestConfigFiles.py8
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions/TestTypes.py20
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions/Two.py3
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py10
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py9
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py7
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py8
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py15
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py18
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py33
-rw-r--r--testsuite/Testsrc/Testlib/TestUtils.py15
-rw-r--r--testsuite/Testsrc/test_code_checks.py8
-rw-r--r--testsuite/Testsrc/test_doc.py39
-rw-r--r--testsuite/common.py66
-rw-r--r--testsuite/ext/exception_messages.py11
-rw-r--r--testsuite/ext/pylint_compat.py280
-rw-r--r--testsuite/ext/ssl_protocols.py25
-rwxr-xr-xtestsuite/install.sh85
-rwxr-xr-xtestsuite/prepare-python.sh8
-rw-r--r--testsuite/pylintrc.conf2
-rw-r--r--testsuite/requirements-26.txt9
-rw-r--r--testsuite/requirements-legacy.txt14
-rw-r--r--testsuite/requirements.txt9
-rwxr-xr-xtestsuite/test.sh9
-rwxr-xr-xtools/export.py4
-rwxr-xr-xtools/upgrade/1.3/migrate_dbstats.py40
-rwxr-xr-xtools/upgrade/1.3/migrate_info.py6
-rwxr-xr-xtools/upgrade/1.3/migrate_perms_to_mode.py12
217 files changed, 6832 insertions, 3185 deletions
diff --git a/.travis.yml b/.travis.yml
index 8b336e7f8..af365b9d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,24 +1,82 @@
+sudo: false
+dist: trusty
language: python
-python:
- - "2.6"
- - "2.7_with_system_site_packages"
-env:
- - WITH_OPTIONAL_DEPS=yes
- - WITH_OPTIONAL_DEPS=no
+
+packages: &build_deps_optional
+ - swig
+ - libxml2-utils
+ - yum
+ - libaugeas-dev
+ - augeas-lenses
+ - libacl1-dev
+ - libssl-dev
+
+matrix:
+ include:
+ - env: PYTHON=2.4 NOSE_EXCLUDE_DIRS=testsuite/Testsrc/Testlib/TestServer
+ addons:
+ apt:
+ sources: [deadsnakes]
+ packages: [python2.4, python2.4-dev]
+ - env: PYTHON=2.5 NOSE_EXCLUDE_DIRS=testsuite/Testsrc/Testlib/TestServer
+ addons:
+ apt:
+ sources: [deadsnakes]
+ packages: [python2.5, python2.5-dev, libbluetooth-dev]
+
+ - python: "2.6"
+ - python: "2.6"
+ env: WITH_OPTIONAL_DEPS=yes
+ addons:
+ apt:
+ packages: *build_deps_optional
+
+ - python: "2.7"
+ - python: "2.7"
+ env: WITH_OPTIONAL_DEPS=yes WITH_SYSTEM_SITE_PACKAGES=yes TEST_SPHINX=yes
+ addons:
+ apt:
+ packages: [*build_deps_optional, python-gamin, python-selinux]
+
+ - python: "3.5"
+ env: WITH_OPTIONAL_DEPS=yes
+ addons:
+ apt:
+ packages: *build_deps_optional
+
+ allow_failures:
+ - python: "3.5"
+
+ fast_finish: true
+
+before_install:
+ - testsuite/prepare-python.sh
+ - if test -d "$HOME/custom-virtualenv/"; then source "$HOME/custom-virtualenv/bin/activate"; fi
install:
- testsuite/install.sh
- - pip install --use-mirrors -e .
+ - pip install -e .
script:
- - nosetests testsuite
+ - testsuite/test.sh
after_failure:
- pip freeze
+
branches:
except:
- maint-1.2
- 1.1.0-stable
+
notifications:
- email: chris.a.st.pierre@gmail.com
irc:
channels:
- "irc.freenode.org#bcfg2"
use_notice: true
+
+cache:
+ directories:
+ - $HOME/.cache/pip
+ - $HOME/.cache/wheels
+ - $HOME/.cache/xml
+
+before_cache:
+ - rm -f $HOME/.cache/pip/log/debug.log
+ - rm -f $HOME/.cache/xml/catalog.xml
diff --git a/README b/README.rst
index bc2f8951c..095378d2c 100644
--- a/README
+++ b/README.rst
@@ -1,5 +1,5 @@
Bcfg2 - A Configuration Management System
------------------------------------------
+=========================================
Bcfg2 (bee-config two) helps system administrators produce a
consistent, reproducible, and verifiable description of their
@@ -13,7 +13,7 @@ Computer Science Division of Argonne National Laboratory.
Bcfg2 is fairly portable. It has been successfully run on:
* AIX, FreeBSD, OpenBSD Mac OS X, OpenSolaris, Solaris
-* Many GNU/Linux distributions, including ArchLinux, Blag, CentOS,
+* Many GNU/Linux distributions, including Arch Linux, Blag, CentOS,
Debian, Fedora, Gentoo, gNewSense, Mandriva, openSUSE, Red Hat/RHEL,
SuSE/SLES, Trisquel, and Ubuntu.
diff --git a/debian/bcfg2-server.install b/debian/bcfg2-server.install
index 3f1a73b06..cea918d00 100644
--- a/debian/bcfg2-server.install
+++ b/debian/bcfg2-server.install
@@ -5,3 +5,4 @@ debian/tmp/usr/lib/python*/*-packages/Bcfg2/Reporting/*
debian/tmp/usr/share/bcfg2/schemas/*
debian/tmp/usr/share/bcfg2/xsl-transforms/*
debian/tmp/usr/share/man/man8/*
+debian/tmp/etc/bash_completion.d/*
diff --git a/debian/bcfg2-utils.install b/debian/bcfg2-utils.install
new file mode 100644
index 000000000..ccddfa1af
--- /dev/null
+++ b/debian/bcfg2-utils.install
@@ -0,0 +1 @@
+tools/* usr/share/bcfg2
diff --git a/debian/bcfg2.install b/debian/bcfg2.install
index a240dac53..eaf58141c 100644
--- a/debian/bcfg2.install
+++ b/debian/bcfg2.install
@@ -7,4 +7,3 @@ debian/tmp/usr/share/man/man5/*
examples/bcfg2.conf usr/share/bcfg2
debian/bcfg2.default usr/share/bcfg2
tools/bcfg2-cron usr/lib/bcfg2
-tools/bcfg2-import-config usr/share/bcfg2
diff --git a/debian/changelog b/debian/changelog
index c41b2ecc2..f6b395624 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,27 @@
+bcfg2 (1.4.0pre2-0.1) UNRELEASED; urgency=low
+
+ * Helper scripts packed in bcfg2-utils
+
+ -- Holger Mueller <zarath@gmx.de> Wed, 07 Aug 2013 21:41:10 +0200
+
+bcfg2 (1.4.0pre2-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Fri, 13 Nov 2015 12:01:49 -0600
+
bcfg2 (1.4.0pre1-0.0) unstable; urgency=low
* New upstream release
-- Sol Jerome <sol.jerome@gmail.com> Mon, 16 Jun 2014 09:36:13 -0500
+bcfg2 (1.3.6-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Thu, 11 Jun 2015 15:30:04 -0500
+
bcfg2 (1.3.5-0.0) unstable; urgency=low
* New upstream release
diff --git a/debian/control b/debian/control
index 452fa3d5f..a5e5c1b3e 100644
--- a/debian/control
+++ b/debian/control
@@ -17,8 +17,8 @@ Build-Depends: debhelper (>= 7.0.50~),
python-m2crypto,
python-doc,
python-mock,
- python-mock-doc
-Build-Depends-Indep: python-support (>= 0.5.3)
+ python-mock-doc,
+ dh-python
Standards-Version: 3.8.0.0
Homepage: http://bcfg2.org/
@@ -35,7 +35,7 @@ Package: bcfg2-server
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 (>= 2.6), python-pyinotify | python-gamin, python-daemon, python-genshi (>= 0.4.4)
Recommends: graphviz, patch
-Suggests: python-cheetah, python-profiler, python-django, mail-transport-agent, bcfg2-doc (= ${binary:Version})
+Suggests: python-cheetah, python-profiler, python-django (>= 1.3), 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.
@@ -44,13 +44,23 @@ Description: Configuration management server
Package: bcfg2-web
Architecture: all
-Depends: ${python:Depends}, ${misc:Depends}, bcfg2-server (= ${binary:Version}), python-django, python-django-south (>= 0.7.5)
+Depends: ${python:Depends}, ${misc:Depends}, bcfg2-server (= ${binary:Version}), python-django (>= 1.3)
Suggests: python-mysqldb, python-psycopg2, python-sqlite, libapache2-mod-wsgi
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-utils
+Architecture: all
+Depends: ${python:Depends}, ${misc:Depends}, bcfg2 (= ${binary:Version})
+Suggests: bcfg2-doc (= ${binary:Version})
+Description: Configuration management helper package
+ Bcfg2 is a configuration management system that generates configuration sets
+ for clients bound by client profiles.
+ bcfg2-utils contains scripts for gethering useful information for config
+ creation.
+
Package: bcfg2-doc
Section: doc
Architecture: all
diff --git a/debian/rules b/debian/rules
index eaf80a4d7..102a340e9 100755
--- a/debian/rules
+++ b/debian/rules
@@ -23,3 +23,7 @@ override_dh_auto_build:
override_dh_auto_clean:
dh_auto_clean
rm -rf build
+
+override_dh_auto_install:
+ dh_auto_install
+ install -m 644 -D tools/bcfg2-completion.bash debian/tmp/etc/bash_completion.d/bcfg2
diff --git a/doc/appendix/guides/authentication.txt b/doc/appendix/guides/authentication.txt
index 93a34c9bc..eba01ee3c 100644
--- a/doc/appendix/guides/authentication.txt
+++ b/doc/appendix/guides/authentication.txt
@@ -145,7 +145,7 @@ Allowed values are:
+-------------------+------------------------------------------+
``cert+password`` is the default. This can be changed by setting the
-``authentication`` parameter in the ``[communcation]`` section of
+``authentication`` parameter in the ``[communication]`` section of
``bcfg2.conf``. For instance, to set ``bootstrap`` mode as the global
default, you would add the following to ``bcfg2.conf``::
diff --git a/doc/appendix/guides/import-existing-ssh-keys.txt b/doc/appendix/guides/import-existing-ssh-keys.txt
index 4e2282044..225844448 100644
--- a/doc/appendix/guides/import-existing-ssh-keys.txt
+++ b/doc/appendix/guides/import-existing-ssh-keys.txt
@@ -40,9 +40,12 @@ files explicity:
<!-- requires a version of openssh that can generate ecdsa keys -->
<Path name="/etc/ssh/ssh_host_ecdsa_key"/>
<Path name="/etc/ssh/ssh_host_ecdsa_key.pub"/>
+ <!-- requires a version of openssh that can generate ed25519 keys -->
+ <Path name="/etc/ssh/ssh_host_ed25519_key"/>
+ <Path name="/etc/ssh/ssh_host_ed25519_key.pub"/>
<Path name='/etc/ssh/ssh_host_dsa_key'/>
- <Path name='/etc/ssh/ssh_host_rsa_key'/>
<Path name='/etc/ssh/ssh_host_dsa_key.pub'/>
+ <Path name='/etc/ssh/ssh_host_rsa_key'/>
<Path name='/etc/ssh/ssh_host_rsa_key.pub'/>
<Path name='/etc/ssh/ssh_host_key'/>
<Path name='/etc/ssh/ssh_host_key.pub'/>
@@ -97,7 +100,7 @@ Now, we pull the ssh host key data for the client out of the uploaded
stats and insert it as host-specific copies of these files in
``/var/lib/bcfg2/SSHBase``.::
- for key in ssh_host_ecdsa_key ssh_host_rsa_key ssh_host_dsa_key ssh_host_key; do
+ for key in ssh_host_ed25519_key ssh_host_ecdsa_key ssh_host_rsa_key ssh_host_dsa_key ssh_host_key; do
sudo bcfg2-admin pull <clientname> Path /etc/ssh/$key
sudo bcfg2-admin pull <clientname> Path /etc/ssh/$key.pub
done
diff --git a/doc/appendix/guides/ubuntu.txt b/doc/appendix/guides/ubuntu.txt
index 24bebf023..fde795594 100644
--- a/doc/appendix/guides/ubuntu.txt
+++ b/doc/appendix/guides/ubuntu.txt
@@ -326,7 +326,7 @@ done for us.::
root@saucy:/var/lib/bcfg2# bcfg2 -vqdn
Configured logging: DEBUG to console; DEBUG to syslog
- {'help': False, 'extra': False, 'ppath': '/var/cache/bcfg2', 'ca': '/etc/ssl/bcfg2.crt', 'rpm_version_fail_action': 'upgrade', 'yum_version_fail_action': 'upgrade', 'retry_delay': '1', 'posix_uid_whitelist': [], 'rpm_erase_flags': ['allmatches'], 'verbose': True, 'certificate': '/etc/ssl/bcfg2.crt', 'paranoid': False, 'rpm_installonly': ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], 'cache': None, 'yum24_autodep': True, 'yum_pkg_verify': True, 'probe_timeout': None, 'yum_installed_action': 'install', 'rpm_verify_fail_action': 'reinstall', 'dryrun': True, 'retries': '3', 'apt_install_path': '/usr', 'quick': True, 'password': 'secret', 'yum24_installed_action': 'install', 'kevlar': False, 'max_copies': 1, 'syslog': True, 'decision_list': False, 'configfile': '/etc/bcfg2.conf', 'remove': None, 'server': 'https://saucy:6789', 'encoding': 'UTF-8', 'timeout': 90, 'debug': True, 'yum24_installonly': ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], 'yum24_erase_flags': ['allmatches'], 'yum24_pkg_checks': True, 'interactive': False, 'apt_etc_path': '/etc', 'rpm_installed_action': 'install', 'yum24_verify_fail_action': 'reinstall', 'omit_lock_check': False, 'yum24_pkg_verify': True, 'serverCN': None, 'file': None, 'apt_var_path': '/var', 'posix_gid_whitelist': [], 'posix_gid_blacklist': [], 'indep': False, 'decision': 'none', 'servicemode': 'default', 'version': False, 'rpm_pkg_checks': True, 'profile': None, 'yum_pkg_checks': True, 'args': [], 'bundle': [], 'posix_uid_blacklist': [], 'user': 'root', 'key': '/etc/ssl/bcfg2.key', 'command_timeout': None, 'probe_exit': True, 'lockfile': '/var/lock/bcfg2.run', 'yum_verify_fail_action': 'reinstall', 'yum24_version_fail_action': 'upgrade', 'yum_verify_flags': [], 'logging': None, 'rpm_pkg_verify': True, 'bundle_quick': False, 'rpm_verify_flags': [], 'yum24_verify_flags': [], 'skipindep': False, 'skipbundle': [], 'portage_binpkgonly': False, 'drivers': ['APK', 'APT', 'Action', 'Blast', 'Chkconfig', 'DebInit', 'Encap', 'FreeBSDInit', 'FreeBSDPackage', 'IPS', 'MacPorts', 'OpenCSW', 'POSIX', 'POSIXUsers', 'Pacman', 'Portage', 'RPM', 'RPMng', 'RcUpdate', 'SELinux', 'SMF', 'SYSV', 'Systemd', 'Upstart', 'VCS', 'YUM', 'YUM24', 'YUMng', 'launchd']}
+ {'help': False, 'extra': False, 'ppath': '/var/cache/bcfg2', 'ca': '/etc/ssl/bcfg2.crt', 'rpm_version_fail_action': 'upgrade', 'yum_version_fail_action': 'upgrade', 'retry_delay': '1', 'posix_uid_whitelist': [], 'rpm_erase_flags': ['allmatches'], 'verbose': True, 'certificate': '/etc/ssl/bcfg2.crt', 'paranoid': False, 'rpm_installonly': ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], 'cache': None, 'yum24_autodep': True, 'yum_pkg_verify': True, 'probe_timeout': None, 'yum_installed_action': 'install', 'rpm_verify_fail_action': 'reinstall', 'dryrun': True, 'retries': '3', 'apt_install_path': '/usr', 'quick': True, 'password': 'secret', 'yum24_installed_action': 'install', 'kevlar': False, 'max_copies': 1, 'syslog': True, 'decision_list': False, 'configfile': '/etc/bcfg2.conf', 'remove': None, 'server': 'https://saucy:6789', 'encoding': 'UTF-8', 'timeout': 90, 'debug': True, 'yum24_installonly': ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], 'yum24_erase_flags': ['allmatches'], 'yum24_pkg_checks': True, 'interactive': False, 'apt_etc_path': '/etc', 'rpm_installed_action': 'install', 'yum24_verify_fail_action': 'reinstall', 'omit_lock_check': False, 'yum24_pkg_verify': True, 'serverCN': None, 'file': None, 'apt_var_path': '/var', 'posix_gid_whitelist': [], 'posix_gid_blacklist': [], 'indep': False, 'decision': 'none', 'service_mode': 'default', 'version': False, 'rpm_pkg_checks': True, 'profile': None, 'yum_pkg_checks': True, 'args': [], 'bundle': [], 'posix_uid_blacklist': [], 'user': 'root', 'key': '/etc/ssl/bcfg2.key', 'command_timeout': None, 'probe_exit': True, 'lockfile': '/var/lock/bcfg2.run', 'yum_verify_fail_action': 'reinstall', 'yum24_version_fail_action': 'upgrade', 'yum_verify_flags': [], 'logging': None, 'rpm_pkg_verify': True, 'bundle_quick': False, 'rpm_verify_flags': [], 'yum24_verify_flags': [], 'skipindep': False, 'skipbundle': [], 'portage_binpkgonly': False, 'drivers': ['APK', 'APT', 'Action', 'Blast', 'Chkconfig', 'DebInit', 'Encap', 'FreeBSDInit', 'FreeBSDPackage', 'IPS', 'MacPorts', 'OpenCSW', 'POSIX', 'POSIXUsers', 'Pacman', 'Portage', 'RPM', 'RPMng', 'RcUpdate', 'SELinux', 'SMF', 'SYSV', 'Systemd', 'Upstart', 'VCS', 'YUM', 'YUM24', 'YUMng', 'launchd']}
Starting Bcfg2 client run at 1374191628.88
Running probe groups
Running: /tmp/tmpEtgdwo
diff --git a/doc/client/tools/vcs.txt b/doc/client/tools/vcs.txt
index fb9c33684..f21d097ff 100644
--- a/doc/client/tools/vcs.txt
+++ b/doc/client/tools/vcs.txt
@@ -8,8 +8,34 @@ VCS Client Tool
.. warning: This tool is currently under development.
-.. note: Currently, the only supported VCS is git.
+.. note: Currently, the only supported VCS is git and svn.
The VCS tool allows you to checkout particular revisions from a VCS
repository on the client to a specified path. The tool requires the
appropriate python libraries for the VCS used to be installed.
+
+See :ref:`server-plugins-generators-rules-vcs` for possible options.
+
+Example usage:
+
+You may want to create a `Rules/paths.xml` with the following:
+
+.. code-block:: xml
+
+ <Rules priority="1">
+ <Path name="/srv/bcfg2" type="vcs"
+ sourceurl="https://github.com/Bcfg2/bcfg2.git"
+ vcstype="git"
+ revision="cf6dfd8ca28e941b1e638ff0fa7e7a0a1ebb6a6f"/>
+ </Rules>
+
+Once the rule is created a client can reference the path from a
+bundle, this path will then be populated from the repository. To
+continue the above example, a file `Bundle/bcfg2.xml` might contain
+this:
+
+.. code-block:: xml
+
+ <Bundle name="bcfg">
+ <Path name="/srv/bcfg2">
+ <Bundle/>
diff --git a/doc/conf.py b/doc/conf.py
index 1da6b3b01..6af9fba05 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -133,7 +133,7 @@ html_theme_options = {
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-html_favicon = 'favicon.ico'
+html_favicon = '_static/favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
diff --git a/doc/development/caching.txt b/doc/development/caching.txt
index 83ec0290f..c8b7aba14 100644
--- a/doc/development/caching.txt
+++ b/doc/development/caching.txt
@@ -67,6 +67,9 @@ Currently known caches are:
| pkg_sets | <Collection.cachekey>`, | | for clients |
| | hash of the initial package selection | | |
+-------------+---------------------------------------+-------------------------------------------------+------------------------------------------------------+
+| Ldap, | Hostname, ``<query name>`` | :func:`processed result of the query | Cached results from the Ldap queries |
+| results, | | <Bcfg2.Server.Plugins.LdapQuery.process_result>`| |
++-------------+---------------------------------------+-------------------------------------------------+------------------------------------------------------+
These are enumerated so that they can be expired as needed by other
plugins or other code points.
diff --git a/doc/development/compat.txt b/doc/development/compat.txt
index 8700c46d3..a6a85cbab 100644
--- a/doc/development/compat.txt
+++ b/doc/development/compat.txt
@@ -113,7 +113,7 @@ with Python 2.4 (and occasionally 2.5). Be sure to read the notes
below, since some of these implementations may be feature-incomplete.
+----------------+--------------------------------+--------------------------------------------+
-| Name | Python 2.4 | Python 2.4+ |
+| Name | Python 2.4 | Python 2.5+ |
+================+================================+============================================+
| formatdate | :func:`email.Utils.formatdate` | :func:`email.utils.formatdate` |
+----------------+--------------------------------+--------------------------------------------+
@@ -129,6 +129,8 @@ below, since some of these implementations may be feature-incomplete.
+----------------+--------------------------------+--------------------------------------------+
| MutableMapping | :class:`UserDict.DictMixin` | :class:`collections.MutableMapping` (2.6+) |
+----------------+--------------------------------+--------------------------------------------+
+| literal_eval | :func:`eval` | :func:`ast.literal_eval` (2.6+) |
++----------------+--------------------------------+--------------------------------------------+
walk_packages
~~~~~~~~~~~~~
@@ -171,6 +173,14 @@ mind.
:class:`collections.MutableMapping` is available in Python 2.6+, and
will be used if available.
+literal_eval
+~~~~~~~~~~~~
+
+:func:`ast.literal_eval` is a safe version of :func:`eval` that will only
+allow delaration of literal strings, ints, list, dicts, etc. This was
+introduced in Python 2.6, and as such Python 2.4 uses the plain-old
+:func:`eval`.
+
Other Symbols
-------------
diff --git a/doc/development/plugins.txt b/doc/development/plugins.txt
index d292c9dd7..0d524973e 100644
--- a/doc/development/plugins.txt
+++ b/doc/development/plugins.txt
@@ -118,7 +118,9 @@ Invalidating Caches
In Bcfg2 1.3.0, some limited :ref:`server-caching` was introduced. If
you are writing a :class:`Bcfg2.Server.Plugin.interfaces.Connector`
plugin that implements
-:func:`Bcfg2.Server.Plugin.interfaces.Connector.get_additional_groups`,
+:func:`Bcfg2.Server.Plugin.interfaces.Connector.get_additional_groups`
+or
+:func:`Bcfg2.Server.Plugin.interfaces.Connector.get_additional_data`,
then you need to be able to invalidate the server metadata cache in
order to be compatible with the ``cautious`` or ``aggressive`` caching
modes.
@@ -140,9 +142,9 @@ called with one string argument, it expires cached data for the named
client.
It's important, therefore, that your Connector plugin can either track
-when changes are made to the group membership it reports, and expire
-cached data appropriately when in ``cautious`` or ``aggressive`` mode;
-or prudently flag an incompatibility with those two modes.
+when changes are made to the data or group membership it reports, and
+expire cached data appropriately when in ``cautious`` or ``aggressive``
+mode; or prudently flag an incompatibility with those two modes.
For examples, see:
diff --git a/doc/exts/xmlschema.py b/doc/exts/xmlschema.py
index 89104c2a6..63ab6f489 100644
--- a/doc/exts/xmlschema.py
+++ b/doc/exts/xmlschema.py
@@ -70,10 +70,9 @@ import lxml.etree
from docutils import nodes
from sphinx import addnodes, roles
from docutils.statemachine import ViewList
-from docutils.parsers.rst import directives
+from docutils.parsers.rst import Directive, directives
from sphinx.util.nodes import make_refnode, split_explicit_title, \
nested_parse_with_titles
-from sphinx.util.compat import Directive
from sphinx.domains import ObjType, Domain
try:
diff --git a/doc/installation/distributions.txt b/doc/installation/distributions.txt
index 5dad4d860..6abf438f0 100644
--- a/doc/installation/distributions.txt
+++ b/doc/installation/distributions.txt
@@ -22,16 +22,14 @@ Just use `apk` to perform the installation ::
.. _Alpine Linux: http://www.alpinelinux.org/
.. _testing: http://git.alpinelinux.org/cgit/aports/tree/testing/bcfg2
-ArchLinux
-=========
+Arch Linux
+==========
Packages for `Arch Linux`_ are available in the Arch User Repository (AUR_).
-Just use `pacman` to perform the installation ::
-
- pacman -S bcfg2 bcfg2-server
+The bcfg2 package includes bcfg2-server.
.. _Arch Linux: http://www.archlinux.org/
-.. _AUR: http://aur.archlinux.org/packages.php?ID=20979
+.. _AUR: http://aur.archlinux.org/packages/bcfg2
Debian
======
diff --git a/doc/installation/prerequisites.txt b/doc/installation/prerequisites.txt
index d89d44894..e6de2a9f6 100644
--- a/doc/installation/prerequisites.txt
+++ b/doc/installation/prerequisites.txt
@@ -56,7 +56,8 @@ Bcfg2 Server
+-------------------------------+----------+--------------------------------+
| python-gamin or pyinotify | Any | gamin or inotify, python |
+-------------------------------+----------+--------------------------------+
-| python-ssl (note | Any | python, backported ssl module |
+| python-ssl (this is included | Any | |
+| in Python2.6 and later) | | |
+-------------------------------+----------+--------------------------------+
| python-setuptools | Any | |
+-------------------------------+----------+--------------------------------+
@@ -72,7 +73,7 @@ reporting, such as Apache + mod_wsgi or nginx.
+-------------------------------+----------+--------------------------------+
| Software | Version | Requires |
+===============================+==========+================================+
-| django | 1.2.0+ | |
+| django | 1.3.0+ | |
+-------------------------------+----------+--------------------------------+
| south | 0.7.5+ | |
+-------------------------------+----------+--------------------------------+
diff --git a/doc/introduction/os-support.txt b/doc/introduction/os-support.txt
index 881fdc6dd..42591b627 100644
--- a/doc/introduction/os-support.txt
+++ b/doc/introduction/os-support.txt
@@ -17,13 +17,13 @@ Bcfg2 is fairly portable. It has been successfully run on:
.. _OpenSolaris: http://opensolaris.org/
.. _Solaris: http://www.sun.com/software/solaris/
-* Many `GNU/Linux`_ distributions, including `Archlinux`_, `Blag`_, `CentOS`_,
+* Many `GNU/Linux`_ distributions, including `Arch Linux`_, `Blag`_, `CentOS`_,
`Debian`_, `Fedora`_, `Gentoo`_, `gNewSense`_, `Mandriva`_,
`OpenSUSE`_, `Red Hat/RHEL`_, `Scientific Linux`_, `SuSE/SLES`_, `Trisquel`_,
and `Ubuntu`_.
.. _GNU/Linux: http://www.gnu.org/gnu/Linux-and-gnu.html
-.. _Archlinux: http://www.archlinux.org
+.. _Arch Linux: http://www.archlinux.org
.. _Blag: http://www.blagblagblag.org/
.. _CentOS: http://www.centos.org/
.. _Debian: http://www.debian.org/
diff --git a/doc/man/bcfg2.conf.txt b/doc/man/bcfg2.conf.txt
index 62c4ac1a8..f6fb32cf5 100644
--- a/doc/man/bcfg2.conf.txt
+++ b/doc/man/bcfg2.conf.txt
@@ -267,7 +267,7 @@ revision information out of your repository for reporting purposes.
Ldap Plugin
+++++++++++
-The Ldap plugin makes it possible to fetch data from an LDAP directory,
+The Ldap plugin makes it possible to fetch data from a LDAP directory,
process it and attach it to your metadata.
Metadata Plugin
@@ -624,7 +624,7 @@ configuration file.
chaincert
Specifies the location of your ssl chaining certificate. This is
- used when pre-existing certifcate hostfiles are found, so that
+ used when pre-existing certificate hostfiles are found, so that
they can be validated and only regenerated if they no longer
meet the specification. If you’re using a self signing CA this
would be the CA cert that you generated.
@@ -718,6 +718,11 @@ Reporting options
Maximum number of children for the reporting collector. Use 0 to
disable the limit. (default is 0)
+ django_settings
+ Arbitrary options for the Django installation. The value expected
+ is a literal python dictionary, that is merged with the already set
+ django settings.
+
See Also
--------
diff --git a/doc/man/bcfg2.txt b/doc/man/bcfg2.txt
index 6df4f9b4f..3810b27d4 100644
--- a/doc/man/bcfg2.txt
+++ b/doc/man/bcfg2.txt
@@ -55,7 +55,7 @@ Options
-b bundles Run only the specified colon-delimited set of
bundles.
-c cachefile Cache a copy of the configuration in cachefile.
---ca-cert=cacert Specifiy the path to the SSL CA certificate.
+--ca-cert=cacert Specify the path to the SSL CA certificate.
-d Enable debugging output.
-e When in verbose mode, display extra entry
information.
diff --git a/doc/releases/1.3.6.txt b/doc/releases/1.3.6.txt
index 757fbf6f5..9ab024674 100644
--- a/doc/releases/1.3.6.txt
+++ b/doc/releases/1.3.6.txt
@@ -30,5 +30,14 @@ This is primarily a bugfix release.
https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-OPTIONS
+* SYSV: change instances of simplename to simplefile
+
+ Previous configurations can be updated using the migration tool.
+
+* Authentication: Reject passwd auth, if authentication is set to "cert"
+* Server/Core: drop privileges even if not running as daemon
+* Packages/Yum.py: Fix dependency resolution logic
+* Handle filesystem secontexts properly for contextless filesystems
+
Special thanks to the following contributors for this release: Michael
-Fenn, Matt Kemp, Alexander Sulfrian, Jonathan Billings.
+Fenn, Matt Kemp, Alexander Sulfrian, Jonathan Billings, Ross Smith.
diff --git a/doc/releases/1.4.0pre2.txt b/doc/releases/1.4.0pre2.txt
index 8acbda8a7..5694fe8b4 100644
--- a/doc/releases/1.4.0pre2.txt
+++ b/doc/releases/1.4.0pre2.txt
@@ -8,7 +8,7 @@
The second prerelease for Bcfg2 1.4.0 is now available at:
- ftp://ftp.mcs.anl.gov/pub/bcfg
+ http://bcfg2.org/download/
Bcfg2 1.4.0pre2 is a prerelease, and contains many new features,
including some that are backwards-incompatible with Bcfg2 1.3.x and
@@ -19,6 +19,7 @@ environments.
* NagiosGen: Add bundles to configuration
* HomeBrew: Initial add of plugin
+* Rules/Defaults: Add possibility to use name of entry in attributes
backwards-incompatible user-facing changes
------------------------------------------
@@ -31,6 +32,20 @@ backwards-incompatible user-facing changes
This fixes potentially long client runs when comparing files that have
diverged significantly.
+* The database options in the config (options and reporting_options in database
+ section) now have to be literal python dictionaries.
+
+ This allows to set arbitrary options with nested settings.
+
+* The Ldap plugin changed significantly. The configuration interface was
+ simplified and new configuration options for the number of retries and the
+ delay in between were added.
+
+ You have to register your ldap queries in the global list, there is no
+ distinction between LdapQueries and LdapSubQueries anymore, the names of
+ your queries default to the class names and the Ldap plugin expires
+ the metadata caches if the config file changes.
+
* Ignore directories containing a .bcfg2-ignore file in various plugins
(Bundler, Defaults, Pkgmgr, Properties, PuppetENC, TemplateHelper, Trigger).
diff --git a/doc/releases/index.txt b/doc/releases/index.txt
index 271fc23cc..911fe070f 100644
--- a/doc/releases/index.txt
+++ b/doc/releases/index.txt
@@ -9,6 +9,8 @@ Release Announcements
.. toctree::
+ 1.4.0pre2
1.4.0pre1
+ 1.3.6
1.3.5
1.3.4
diff --git a/doc/server/encryption.txt b/doc/server/encryption.txt
index db5e2ae29..c186bc31d 100644
--- a/doc/server/encryption.txt
+++ b/doc/server/encryption.txt
@@ -225,16 +225,17 @@ be able to decrypt everything.) In this case, you want to enable lax
decryption in the ``[encryption]`` section of ``bcfg2.conf``::
[encryption]
- decrypt = lax
+ lax_decryption = true
This causes a failed decrypt to produce a warning only, not an error.
This can be overridden by individual XML files by setting
-``decrypt="strict"`` on the top-level tag (or, vice-versa; if strict
-is the default an XML file can specify ``decrypt="lax"``.
+``lax_decryption="false"`` on the top-level tag (or, vice-versa; if
+strict is the default an XML file can specify
+``lax_decryption="true"``.
Note that you could, for instance, set lax decryption by default, and
-then set strict decryption on individual files.
+then disable it on individual files.
Encryption API
==============
diff --git a/doc/server/plugins/connectors/grouplogic.txt b/doc/server/plugins/connectors/grouplogic.txt
index abf425202..dc59e4e0e 100644
--- a/doc/server/plugins/connectors/grouplogic.txt
+++ b/doc/server/plugins/connectors/grouplogic.txt
@@ -92,9 +92,9 @@ generate the tedious config:
.. code-block:: xml
<GroupLogic xmlns:py="http://genshi.edgewall.org/">
- <py:for each="component in metadata.query.all_groups_in_category("webapp-component")>
+ <py:for each="component in metadata.query.all_groups_in_category('webapp-component')">
<Group name="${component}">
- <py:for each="env in metadata.query.all_groups_in_category("environment")>
+ <py:for each="env in metadata.query.all_groups_in_category('environment')">
<Group name="${env}">
<Group name="${component}-${env}"/>
</Group>
diff --git a/doc/server/plugins/connectors/templatehelper.txt b/doc/server/plugins/connectors/templatehelper.txt
index d113dcab7..e24ba10cb 100644
--- a/doc/server/plugins/connectors/templatehelper.txt
+++ b/doc/server/plugins/connectors/templatehelper.txt
@@ -54,9 +54,9 @@ 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 have, as attributes, all symbols listed in
+Specific helpers can be referred to in templates as
+``metadata.TemplateHelper[<modulename>]``. That returns a HelperModule
+object which will have, as attributes, all symbols listed in
``__export__``. For example, consider this helper module::
__export__ = ["hello"]
diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt
index c991f20c9..1b2fec834 100644
--- a/doc/server/plugins/generators/cfg.txt
+++ b/doc/server/plugins/generators/cfg.txt
@@ -275,9 +275,7 @@ Several variables are pre-defined inside templates:
| | <server-plugins-grouping-metadata-clientmetadata>` |
+-------------+--------------------------------------------------------+
| name | The value of the ``name`` attribute as specified in |
-| | the Path entry in Bcfg2. If an :ref:`altsrc |
-| | <server-plugins-structures-altsrc>` attribute is used, |
-| | then ``name`` will be the value of that attribute. |
+| | the Path entry in Bcfg2. |
+-------------+--------------------------------------------------------+
| source_path | The path to the template file on the filesystem |
+-------------+--------------------------------------------------------+
@@ -490,7 +488,7 @@ complex:
.. code-block:: xml
- <PrivateKey category="environment"/>
+ <PrivateKey category="environment">
<Params bits="1024" type="dsa"/>
<Group name="secure">
<Passphrase encrypted="secure">U2FsdGVkX19xACol83uyPELP94s4CmngD12oU6PLLuE=</Passphrase>
@@ -657,7 +655,7 @@ paths.
`sslkey.xml`_ for details on how to change the key type and size.)
#. Similarly, create `sslcert.xml`_ in
- ``Cfg/etc/pki/tls/certs/localhost.cfg/``, containing the following:
+ ``Cfg/etc/pki/tls/certs/localhost.crt/``, containing the following:
.. code-block:: xml
diff --git a/doc/server/plugins/generators/nagiosgen.txt b/doc/server/plugins/generators/nagiosgen.txt
index 1ccdd66c1..47bba8cc2 100644
--- a/doc/server/plugins/generators/nagiosgen.txt
+++ b/doc/server/plugins/generators/nagiosgen.txt
@@ -29,7 +29,6 @@ Create default host, and group specs in:
check_period 24x7
contact_groups admins
event_handler_enabled 1
- failure_prediction_enabled 1
flap_detection_enabled 1
initial_state o
max_check_attempts 10
@@ -195,7 +194,4 @@ moderation.
``NagiosGen/config.xml`` replaces the files
``Properties/NagiosGen.xml`` and ``NagiosGen/parents.xml`` in older
versions of Bcfg2; your old configs can be migrated using the
-``nagiosgen-convert.py`` tool. The plugin does contain a
-backwards-compatibility layer for those older config files, but
-``NagiosGen/config.xml`` must exist (even if empty) for the plugin to
-function.
+``nagiosgen-convert.py`` tool.
diff --git a/doc/server/plugins/generators/packages.txt b/doc/server/plugins/generators/packages.txt
index 2fe71f895..5e14d3be5 100644
--- a/doc/server/plugins/generators/packages.txt
+++ b/doc/server/plugins/generators/packages.txt
@@ -213,7 +213,7 @@ something like this:
Packages plugin to add recommended packages by adding the
:xml:attribute:`SourceType:recommended` attribute, e.g.:
- .. code-block:: xml
+ .. code-block:: none
<Source type="apt" recommended="true" ...>
@@ -222,9 +222,9 @@ something like this:
setting the :xml:attribute:`SourceType:essential`
attribute to *false*:
- .. code-block:: xml
+ .. code-block:: none
- <Source type="apt" essential="false" ...>
+ <Source type="apt" essential="false" ...>
Yum sources can be similarly specified:
@@ -483,6 +483,59 @@ See :ref:`configuration` for more details on these options.
.. _native-yum-libraries:
+Package Groups
+==============
+
+Some packaging systems provide package groups. To include a package
+group, use the :xml:attribute:`PackageStructure:group` attribute of
+the :xml:element:`Package` tag.
+
+pac
+---
+
+.. versionadded:: 1.4.0
+
+Pacman `groups <https://www.archlinux.org/groups/>`_ are supported:
+
+.. code-block:: xml
+
+ <Package group="base"/>
+
+yum
+---
+
+Yum package groups are supported by both the native Yum libraries and
+Bcfg2's internal dependency resolver. 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
+:xml:attribute:`PackageStructure:type` attribute:
+
+.. code-block:: xml
+
+ <Package group="development" type="optional"/>
+ <Package group="Administration Tools" type="mandatory"/>
+
+Valid values of "type" are:
+
+* ``mandatory``: Only install mandatory packages in the group.
+* ``default``: Install default packages from the group (the default).
+* ``optional`` or ``all``: Install all packages in the group,
+ including mandatory, default, and optional packages.
+
+See :xml:type:`PackageStructure` for details.
+
+You can view the packages in a group by category with the ``yum
+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
+
Using Native Yum Libraries
==========================
@@ -546,43 +599,6 @@ generally be overridden:
* ``reposdir`` is set to ``/dev/null`` to prevent the server's Yum
configuration from being read; do not change this.
-Package Groups
---------------
-
-Yum package groups are supported by both the native Yum libraries and
-Bcfg2's internal dependency resolver. To include a package group, use
-the :xml:attribute:`PackageStructure:group` attribute of the
-:xml:element:`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
-:xml:attribute:`PackageStructure:type` attribute:
-
-.. code-block:: xml
-
- <Package group="development" type="optional"/>
- <Package group="Administration Tools" type="mandatory"/>
-
-Valid values of "type" are:
-
-* ``mandatory``: Only install mandatory packages in the group.
-* ``default``: Install default packages from the group (the default).
-* ``optional`` or ``all``: Install all packages in the group,
- including mandatory, default, and optional packages.
-
-See :xml:type:`PackageStructure` for details.
-
-You can view the packages in a group by category with the ``yum
-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
-
Abstract Package Tags
---------------------
diff --git a/doc/server/plugins/generators/rules.txt b/doc/server/plugins/generators/rules.txt
index 86478a5ae..7aeec6990 100644
--- a/doc/server/plugins/generators/rules.txt
+++ b/doc/server/plugins/generators/rules.txt
@@ -248,10 +248,13 @@ Manage symlinks.
:onlyattrs: to
:requiredattrs: to
+.. _server-plugins-generators-rules-vcs:
+
vcs
^^^
-Check out the specified VCS repository to the given path.
+Check out the specified VCS repository to the given path. See
+:ref:`client-tools-vcs` for more details.
.. xml:type:: PathType
:nochildren:
@@ -509,3 +512,23 @@ you'd have to explicitly specify ``<Service name="bcfg2.*".../>``.
Note that only one Rule can apply to any abstract entry, so you cannot
specify multiple regexes to match the same rule.
+
+Replacing the name of the Entry in Attributes
+=============================================
+
+If you are using regular expressions to match the abstract configuration
+entries, you may need the concrete name of the entry in some attributes.
+To use this feature, you have to enable it. It is only useful, if used
+together with regex matching. ::
+
+ [rules]
+ regex = yes
+ replace_name = yes
+
+You now can write something like that in your xml file:
+
+.. code-block:: xml
+
+ <POSIXUser name='.*' home='/somewhere/%{name}'/>
+
+``%{name}`` will be correctly replaced with the username for each POSIXUser.
diff --git a/doc/server/plugins/generators/sshbase.txt b/doc/server/plugins/generators/sshbase.txt
index 540cc1e06..26c1a8121 100644
--- a/doc/server/plugins/generators/sshbase.txt
+++ b/doc/server/plugins/generators/sshbase.txt
@@ -14,8 +14,8 @@ record for the current system.
It has two functions:
-* Generating new ssh keys -- When a client requests a ecdsa, dsa, rsa,
- or v1 key, and there is no existing key in the repository, one is
+* Generating new ssh keys -- When a client requests a key (v1, rsa,
+ ecdsa, etc.), and there is no existing key in the repository, one is
generated.
* Maintaining the ``ssh_known_hosts`` file -- all current known public
@@ -73,6 +73,7 @@ SSHbase currently supports the following key formats:
* RSA2 (``ssh_host_rsa_key``, ``ssh_host_rsa_key.pub``)
* DSA (``ssh_host_dsa_key``, ``ssh_host_dsa_key.pub``)
* ECDSA (``ssh_host_ecdsa_key``, ``ssh_host_ecdsa_key.pub``)
+* Ed25519 (``ssh_host_ed25519_key``, ``ssh_host_ed25519_key.pub``)
Group-specific keys
===================
@@ -143,19 +144,19 @@ control the permissions and other metadata for the keys and
Default permissions are as follows:
-+----------------------------------+-------+-------+------+-----------+----------+----------+
-| File | owner | group | mode | sensitive | paranoid | encoding |
-+==================================+=======+=======+======+===========+==========+==========+
-| ssh_known_hosts | root | root | 0644 | false | false | None |
-+----------------------------------+-------+-------+------+-----------+----------+----------+
-| ssh_host_key | root | root | 0600 | false | false | base64 |
-+----------------------------------+-------+-------+------+-----------+----------+----------+
-| ssh_host_key.pub | root | root | 0644 | false | false | base64 |
-+----------------------------------+-------+-------+------+-----------+----------+----------+
-| ssh_host_[rsa|dsa|ecdsa]_key | root | root | 0600 | false | false | None |
-+----------------------------------+-------+-------+------+-----------+----------+----------+
-| ssh_host_[rsa|dsa|ecdsa]_key.pub | root | root | 0644 | false | false | None |
-+----------------------------------+-------+-------+------+-----------+----------+----------+
++------------------------------------------+-------+-------+------+-----------+----------+----------+
+| File | owner | group | mode | sensitive | paranoid | encoding |
++==========================================+=======+=======+======+===========+==========+==========+
+| ssh_known_hosts | root | root | 0644 | false | false | None |
++------------------------------------------+-------+-------+------+-----------+----------+----------+
+| ssh_host_key | root | root | 0600 | false | false | base64 |
++------------------------------------------+-------+-------+------+-----------+----------+----------+
+| ssh_host_key.pub | root | root | 0644 | false | false | base64 |
++------------------------------------------+-------+-------+------+-----------+----------+----------+
+| ssh_host_[rsa|dsa|ecdsa|ed25519]_key | root | root | 0600 | false | false | None |
++------------------------------------------+-------+-------+------+-----------+----------+----------+
+| ssh_host_[rsa|dsa|ecdsa|ed25519]_key.pub | root | root | 0644 | false | false | None |
++------------------------------------------+-------+-------+------+-----------+----------+----------+
Note that the ``sensitive`` attribute is false, even for private keys,
in order to permit :ref:`pulling with bcfg2-admin
diff --git a/doc/server/plugins/grouping/ldap.txt b/doc/server/plugins/grouping/ldap.txt
index 90590a272..abbd5e005 100644
--- a/doc/server/plugins/grouping/ldap.txt
+++ b/doc/server/plugins/grouping/ldap.txt
@@ -7,7 +7,7 @@ Ldap
====
.. warning::
- This plugin is considered experimental and has known issues (see below).
+ This plugin is considered experimental.
Purpose
-------
@@ -33,39 +33,38 @@ next section.
Configuration
-------------
-As processing LDAP search results can get pretty complex, the configuration has
+As processing LDAP search results can get pretty complex, the configuration has
to be written in Python.
Here is a minimal example to get you started::
- from Bcfg2.Server.Plugins.Ldap import LdapConnection, LdapQuery, LdapSubQuery, register_query
-
- conn_default = LdapConnection()
- conn_default.binddn = "uid=example,ou=People,dc=example,dc=com"
- conn_default.bindpw = "foobat"
-
- @register_query
+ from Bcfg2.Server.Plugins.Ldap import LdapConnection, LdapQuery
+
+ __queries__ = ['ExampleQuery']
+
+ conn_default = LdapConnection(
+ binddn="uid=example,ou=People,dc=example,dc=com",
+ bindpw = "foobat")
+
class ExampleQuery(LdapQuery):
- name = "example"
base = "ou=People,dc=example,dc=com"
scope = "one"
attrs = ["cn", "uid"]
connection = conn_default
-
+
def prepare_query(self, metadata):
self.filter = "(personalServer=" + metadata.hostname + ")"
-
+
def process_result(self, metadata):
if not self.result:
admin_uid = None
admin_name = "This server has no admin."
- return {
+ return {
"admin_uid" : self.result[0][1]["uid"],
"admin_name" : self.result[0][1]["cn"]
}
-The first line provides three classes for dealing with connections and queries
-(details below) and a decorator function for registering your queries with the plugin.
+The first line provides the two required classes for dealing with connections and queries.
In this example our LDAP directory has a number of user objects in it. Each of those
may have a personal server they administer. Whenever metadata for this machine is being
@@ -73,7 +72,40 @@ generated by the Bcfg2 server, the UID and name of the admin are retrieved from
In your bundles and config templates, you can access this data via the metadata object::
- ${metadata.Ldap["example"]["admin_name"]}
+ ${metadata.Ldap["ExampleQuery"]["admin_name"]}
+
+Connection retry
+++++++++++++++++
+
+If the LDAP server is down during a request, the LDAP plugin tries to reconnect after a
+short delay. By default, it waits 3 seconds during the retries and tries to reconnect
+up to three times.
+
+If you wish, you could customize these values in your ``bcfg2.conf``::
+
+ [ldap]
+ retries = 3
+ retry_delay = 3.0
+
+Caching
++++++++
+
+This module could not know, if a value changed on the LDAP server. So it does not cache
+the results of the LDAP queries by default.
+
+You could enable the cache of the results in your ``bcfg2.conf``:
+
+ [ldap]
+ cache = on
+
+If you enable the caching, you have to expire it manually. This module provides a XML-RPC
+method for this purpose: :func:`Ldap.expire_cache
+<Bcfg2.Server.Plugins.Ldap.expire_cache>`.
+
+Even without enabling caching, the results of the LDAP queries are cached, but are
+discarded before each client run. If you access the Ldap results of different client, you
+may get cached results of the last run of this client. If you do not want this behaviour,
+you can disable the caching completely by setting it to ``off``.
Class reference
---------------
@@ -83,24 +115,41 @@ LdapConnection
.. class:: LdapConnection
- This class represents an LDAP connection. Every query must be associated with exactly
- one connection.
-
-.. attribute:: LdapConnection.binddn
-
+ This class represents an LDAP connection. Every query must be associated
+ with exactly one connection.
+
+.. attribute:: LdapConnection.binddn
+
DN used to authenticate against LDAP (required).
-
+
.. attribute:: LdapConnection.bindpw
-
+
Password for the previously mentioned **binddn** (required).
-
+
.. attribute:: LdapConnection.host
-
+
Hostname of host running the LDAP server (defaults to "localhost").
.. attribute:: LdapConnection.port
-
- Port where LDAP server is listening (defaults to 389).
+
+ Port where LDAP server is listening (defaults to 389). If you use
+ port 636 this module will use ldaps to connect to the server.
+
+.. attribute:: LdapConnection.uri
+
+ LDAP URI of the LDAP server to connect to. This is prefered over
+ :attr:`LdapConnection.host` and :attr:`LdapConnection.port`.
+
+ .. note::
+
+ If you are using ldaps you may have to specify additional options
+ for enabling the certificate validation or setting the path for
+ the trusted certificates with :attr:`LdapConnection.options`.
+
+.. attribute:: LdapConnection.options
+
+ Arbitrary options for the LDAP connection. You should specify it
+ as a dict and use the ``OPT_*`` constants from ``python-ldap``.
You may pass any of these attributes as keyword arguments when creating the connection object.
@@ -108,143 +157,129 @@ LdapQuery
+++++++++
.. class:: LdapQuery
-
+
This class defines a single query that may adapt itself depending on the current metadata.
.. attribute:: LdapQuery.attrs
-
+
Can be used to retrieve only a certain subset of attributes. May either be a list of
strings (attribute names) or ``None``, meaning all attributes (defaults to ``None``).
.. attribute:: LdapQuery.base
-
- This is the search base. Only LDAP entries below this DN will be included in your
+
+ This is the search base. Only LDAP entries below this DN will be included in your
search results (required).
-
+
.. attribute:: LdapQuery.connection
-
+
Set this to an instance of the LdapConnection class (required).
.. attribute:: LdapQuery.filter
-
+
LDAP search filter used to narrow down search results (defaults to ``(objectClass=*)``).
.. attribute:: LdapQuery.name
-
+
This will be used as the dictionary key that provides access to the query results from
- the metadata object (``metadata.Ldap["NAMEGOESHERE"]``) (required).
+ the metadata object: ``metadata.Ldap["NAMEGOESHERE"]`` (defaults to the class name).
.. attribute:: LdapQuery.scope
-
- Set this to one of "base", "one" or "sub" to specify LDAP search depth (defaults to "sub").
+
+ Set this to one of "base", "one" or "sub" to specify LDAP search depth (defaults to "sub").
.. method:: LdapQuery.is_applicable(self, metadata)
-
+
You can override this method to indicate whether this query makes sense for a given
set of metadata (e.g. you need a query only for a certain bundle or group).
-
+
(defaults to returning True)
-
-.. method:: LdapQuery.prepare_query(self, metadata)
-
+
+.. method:: LdapQuery.prepare_query(self, metadata, \**kwargs)
+
Override this method to alter the query prior to execution. This is useful if your filter
depends on the current metadata, e.g.::
-
+
self.filter = "(cn=" + metadata.hostname + ")"
-
+
(defaults to doing nothing)
-.. method:: LdapQuery.process_result(self, metadata)
-
+.. method:: LdapQuery.process_result(self, metadata, \**kwargs)
+
You will probably override this method in every query to reformat the results from LDAP.
The raw result is stored in ``self.result``, you must return the altered data. Note that LDAP
search results are presented in this structure::
-
+
(
("DN of first entry returned",
{
"firstAttribute" : 1,
"secondAttribute" : 2,
- }
+ }
),
("DN of second entry returned",
{
"firstAttribute" : 1,
"secondAttribute" : 2,
- }
+ }
),
)
-
+
Therefore, to return just the value of the firstAttribute of the second object returned,
you'd write::
-
+
return self.result[1][1][0]
-
+
(defaults to returning ``self.result`` unaltered)
-LdapSubQuery
-++++++++++++
-
-.. class:: LdapSubQuery
-
- Sometimes you need more than one query to obtain the data you need (e.g. use the first
- query to return all websites running on metadata.hostname and another query to find all
- customers that should have access to those sites).
-
- LdapSubQueries are the same as LdapQueries, except for that the methods
-
- * ``get_result()``
- * ``prepare_query()``
- * ``process_result()``
-
- allow any additional keyword arguments that may contain additional data as needed. Note
- that ``get_result()`` will call ``prepare_query()`` and ``process_result()`` for you,
- so you shouldn't ever need to invoke these yourself, just override them.
-
-Here is another example that uses LdapSubQuery::
-
- class WebSitesQuery(LdapSubQuery):
- name = "web_sites"
+.. method:: LdapQuery.get_result(self, metadata, \**kwargs)
+
+ This executes the query. First it will call ``prepare_query()`` for you, then it will try
+ to execute the query with the specified connection and last it will call ``process_result()``
+ and return that return value.
+
+If you use a LdapQuery class by yourself, you could pass additional keyword arguments to
+``get_result()``. It will call ``prepare_query()`` and ``process_result()`` for you and
+also supply this additional arguments to this methods.
+
+Here is an example::
+
+ __queries__ = ['WebPackageQuery']
+
+ class WebSitesQuery(LdapQuery):
filter = "(objectClass=webHostingSite)"
attrs = ["dc"]
connection = conn_default
-
+
def prepare_query(self, metadata, base_dn):
self.base = base_dn
-
- def process_result(self, metadata):
+
+ def process_result(self, metadata, **kwargs):
[...] # build sites dict from returned dc attributes
return sites
-
- @register_query
+
class WebPackagesQuery(LdapQuery):
- name = "web_packages"
base = "dc=example,dc=com"
attrs = ["customerId"]
connection = conn_default
-
+
def prepare_query(self, metadata):
self.filter = "(&(objectClass=webHostingPackage)(cn:dn:=" + metadata.hostname + "))"
-
+
def process_result(self, metadata):
customers = {}
for customer in self.result:
dn = customer[0]
cid = customer[1]["customerId"][0]
- customers[cid]["sites"] = WebSitesQuery().get_result(metadata, base_dn = dn)
+ customers[cid]["sites"] = WebSitesQuery().get_result(metadata, base_dn=dn)
return customers
This example assumes that we have a number of webhosting packages that contain various
-sites. We need a first query ("web_packages") to get a list of the packages our customers
-have and another query for each of those to find out what sites are contained in each
-package. The magic happens in the second class where ``WebSitesQuery.get_result()`` is
-called with the additional ``base_dn`` parameter that allows our LdapSubQuery to only
+sites. We need the ``WebPackagesQuery`` to get a list of the packages our customers
+have and another query for each of those to find out what sites are contained in each
+package. The magic happens in the second class where ``WebSitesQuery.get_result()`` is
+called with the additional ``base_dn`` parameter that allows our LdapQuery to only
search below that DN.
-.. warning::
- Do NOT apply the ``register_query`` decorator to LdapSubQueries.
-
-Known Issues
-------------
-
-* At this point there is no support for SSL/TLS.
+You do not need to add all LdapQueries to the ``__queries__`` list. Only add those to
+that list, that should be called automatically and whose results should be added to the
+client metadata.
diff --git a/doc/server/plugins/grouping/metadata.txt b/doc/server/plugins/grouping/metadata.txt
index 832b1a13f..f0ff2b29c 100644
--- a/doc/server/plugins/grouping/metadata.txt
+++ b/doc/server/plugins/grouping/metadata.txt
@@ -175,6 +175,9 @@ groups:
<Group name="selinux-enabled" negate="true"/>
</Client>
+Negated groups can also be used to declare other Group assignments,
+but not to declare Bundle assignments.
+
.. note::
Nested Group conditionals, Client tags, and negated Group tags are
diff --git a/doc/server/plugins/structures/bundler/index.txt b/doc/server/plugins/structures/bundler/index.txt
index f8962d42c..afdbbecf2 100644
--- a/doc/server/plugins/structures/bundler/index.txt
+++ b/doc/server/plugins/structures/bundler/index.txt
@@ -269,7 +269,7 @@ or alternately
<Path name="/etc/bacula/bconsole.conf"/>
<Path name="/etc/bacula/bacula-fd.conf"/>
<Path name="/etc/bacula/bacula-sd.conf"/>
- <py:if="metadata.hostname == 'foo.bar.com'">
+ <py:if test="metadata.hostname == 'foo.bar.com'">
<Path name="/etc/bacula/bacula-dir.conf"/>
</py:if>
</Bundle>
diff --git a/doc/server/plugins/structures/defaults.txt b/doc/server/plugins/structures/defaults.txt
index 58b9feddb..9d37b8e64 100644
--- a/doc/server/plugins/structures/defaults.txt
+++ b/doc/server/plugins/structures/defaults.txt
@@ -29,3 +29,10 @@ on Fedora 15 and the ``chkconfig`` tool on Fedora 14, you could do::
If you were to specify a ``type`` attribute for a Service entry in
Rules (or a ``type`` attribute for a BoundService entry in Bundler),
that would take precendence over the default.
+
+Like :ref:`server-plugins-generators-rules`, Defaults can also replace
+``%{name}`` in attributes with the real name of the entry. To enable this,
+add the following setting to ``bcfg2.conf``::
+
+ [defaults]
+ replace_name = yes
diff --git a/doc/unsorted/vim_snippet.txt b/doc/unsorted/vim_snippet.txt
index 4598b5c1d..537bb00a2 100644
--- a/doc/unsorted/vim_snippet.txt
+++ b/doc/unsorted/vim_snippet.txt
@@ -13,7 +13,7 @@ that allow quick composition of bundles and base files.
#. Install it using the install instructions (unzip snipMate.zip -d ~/.vim or equivalent, e.g. $HOME\vimfiles on Windows)
#. Add the following to ``~/.vim/snippets/xml.snippets``
- .. code-block:: cl
+ .. code-block:: none
# Bundle
snippet <Bundle
diff --git a/man/bcfg2.1 b/man/bcfg2.1
index 5b9449fda..73985abc0 100644
--- a/man/bcfg2.1
+++ b/man/bcfg2.1
@@ -109,7 +109,7 @@ bundles.
Cache a copy of the configuration in cachefile.
.TP
.BI \-\-ca\-cert\fB= cacert
-Specifiy the path to the SSL CA certificate.
+Specify the path to the SSL CA certificate.
.TP
.B \-d
Enable debugging output.
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index 13000a719..87a710760 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -286,7 +286,7 @@ a Mercurial version control backend. Currently, it enables you to get
revision information out of your repository for reporting purposes.
.SS Ldap Plugin
.sp
-The Ldap plugin makes it possible to fetch data from an LDAP directory,
+The Ldap plugin makes it possible to fetch data from a LDAP directory,
process it and attach it to your metadata.
.SS Metadata Plugin
.sp
@@ -661,7 +661,7 @@ private key is stored unencrypted.
.TP
.B chaincert
Specifies the location of your ssl chaining certificate. This is
-used when pre\-existing certifcate hostfiles are found, so that
+used when pre\-existing certificate hostfiles are found, so that
they can be validated and only regenerated if they no longer
meet the specification. If you’re using a self signing CA this
would be the CA cert that you generated.
@@ -778,6 +778,11 @@ Turn on Django debugging.
.B max_children
Maximum number of children for the reporting collector. Use 0 to
disable the limit. (default is 0)
+.TP
+.B django_settings
+Arbitrary options for the Django installation. The value expected
+is a literal python dictionary, that is merged with the already set
+django settings.
.UNINDENT
.UNINDENT
.UNINDENT
diff --git a/misc/bcfg2-selinux.spec b/misc/bcfg2-selinux.spec
index e3701bbdd..4c9ac9e91 100644
--- a/misc/bcfg2-selinux.spec
+++ b/misc/bcfg2-selinux.spec
@@ -12,7 +12,7 @@
#
# Don't forget to change the Release: tag below to something like 0.1
#%%global _rc 1
-%global _pre pre1
+%global _pre pre2
%global _pre_rc %{?_pre:.pre%{_pre}}%{?_rc:.rc%{_rc}}
Name: bcfg2-selinux
diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec
index 357afed4e..443e0d27a 100644
--- a/misc/bcfg2.spec
+++ b/misc/bcfg2.spec
@@ -18,11 +18,11 @@
# characters from the appropriate line below.
#
# Don't forget to change the Release: tag below to something like 0.1
-#%%global _rc 1
-%global _pre pre1
+#%%global _rc rc1
+%global _pre pre2
%global _nightly 1
%global _date %(date +%Y%m%d)
-%global _pre_rc %{?_pre:pre%{_pre}}%{?_rc:rc%{_rc}}
+%global _pre_rc %{?_pre:%{_pre}}%{?_rc:%{_rc}}
# cherrypy 3.3 actually doesn't exist yet, but 3.2 has bugs that
# prevent it from working:
@@ -56,6 +56,8 @@ BuildRequires: python
BuildRequires: python-devel
BuildRequires: python-lxml
BuildRequires: python-boto
+BuildRequires: python-argparse
+BuildRequires: python-jinja2
%if 0%{?suse_version}
BuildRequires: python-M2Crypto
BuildRequires: python-Genshi
@@ -83,9 +85,9 @@ BuildRequires: mock
BuildRequires: m2crypto
# EPEL uses the properly-named python-django starting with EPEL7
%if 0%{?rhel} && 0%{?rhel} > 6
-BuildRequires: python-django
+BuildRequires: python-django >= 1.3
%else
-BuildRequires: Django
+BuildRequires: Django >= 1.3
%endif
BuildRequires: python-genshi
BuildRequires: python-cheetah
@@ -137,6 +139,8 @@ BuildRequires: systemd-units
Requires: python-ssl
%endif
Requires: libselinux-python
+Requires: pylibacl
+Requires: python-argparse
%if 0%{?fedora} >= 16
Requires(post): systemd-units
@@ -192,6 +196,7 @@ Group: System Environment/Daemons
%endif
Requires: bcfg2 = %{version}-%{release}
Requires: python-lxml >= 1.2.1
+Requires: python-genshi
%if 0%{?suse_version}
Requires: python-pyinotify
Requires: python-python-daemon
@@ -298,15 +303,15 @@ Requires: bcfg2-server = %{version}-%{release}
Requires: httpd
%if 0%{?suse_version}
Group: System/Management
-Requires: python-django >= 1.2
+Requires: python-django >= 1.3
Requires: python-django-south >= 0.7
%else
Group: System Tools
# EPEL uses the properly-named python-django starting with EPEL7
%if 0%{?rhel} && 0%{?rhel} > 6
-Requires: python-django
+Requires: python-django > 1.3
%else
-Requires: Django >= 1.2
+Requires: Django >= 1.3
Requires: Django-south >= 0.7
%endif
Requires: bcfg2-server
@@ -662,7 +667,7 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
# Required for EL5 and OpenSUSE
%defattr(-,root,root,-)
%endif
-%doc COPYRIGHT LICENSE README
+%doc COPYRIGHT LICENSE README.rst
%{_mandir}/man1/bcfg2.1*
%{_mandir}/man5/bcfg2.conf.5*
%ghost %attr(600,root,root) %config(noreplace,missingok) %{_sysconfdir}/bcfg2.cert
@@ -714,7 +719,9 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
%{python_sitelib}/Bcfg2/Server
%{python_sitelib}/Bcfg2/Reporting
%{python_sitelib}/Bcfg2/manage.py*
+%if %{build_cherry_py}
%exclude %{python_sitelib}/Bcfg2/Server/CherryPyCore.py*
+%endif
%dir %{_datadir}/bcfg2
%{_datadir}/bcfg2/schemas
diff --git a/osx/Introduction.txt b/osx/Introduction.txt
index 79b935f23..7e43b72e8 100644
--- a/osx/Introduction.txt
+++ b/osx/Introduction.txt
@@ -11,6 +11,6 @@ Finally, due to the rapid pace of updates on modern networks, client systems are
Bcfg2 is fairly portable. It has been successfully run on:
AIX, FreeBSD, OpenBSD, Mac OS X, OpenSolaris, Solaris
- Many GNU/Linux distributions, including ArchLinux Blag, CentOS, Debian, Fedora, Gentoo, gNewSense, Mandriva, openSUSE, Red Hat/RHEL, SuSE/SLES, Trisquel, and Ubuntu.
+ Many GNU/Linux distributions, including Arch Linux, Blag, CentOS, Debian, Fedora, Gentoo, gNewSense, Mandriva, openSUSE, Red Hat/RHEL, SuSE/SLES, Trisquel, and Ubuntu.
Bcfg2 should run on any POSIX compatible operating system, however direct support for an operating system's package and service formats are limited by the currently available client tools (new client tools are pretty easy to add). There is also an incomplete but more exact list of platforms on which Bcfg2 works.
diff --git a/osx/Makefile b/osx/Makefile
index 174eb53c8..e2fcf0c19 100644
--- a/osx/Makefile
+++ b/osx/Makefile
@@ -28,7 +28,7 @@ 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.4.0pre1
+BCFGVER = 1.4.0pre2
MAJOR = 1
MINOR = 40
diff --git a/osx/macports/Portfile b/osx/macports/Portfile
index 05ce40bf0..93d27295c 100644
--- a/osx/macports/Portfile
+++ b/osx/macports/Portfile
@@ -5,7 +5,7 @@ PortSystem 1.0
PortGroup python26 1.0
name bcfg2
-version 1.4.0pre1
+version 1.4.0pre2
categories sysutils python
maintainers gmail.com:sol.jerome
license BSD
diff --git a/reports/reports.wsgi b/reports/reports.wsgi
index 7113b3308..979bcd389 100644
--- a/reports/reports.wsgi
+++ b/reports/reports.wsgi
@@ -4,9 +4,6 @@ import Bcfg2.DBSettings
config_parsed = False
-import django.core.handlers.wsgi
-
-
def application(environ, start_response):
global config_parsed
@@ -18,4 +15,9 @@ def application(environ, start_response):
Bcfg2.Options.get_parser().parse()
config_parsed = True
- return django.core.handlers.wsgi.WSGIHandler()(environ, start_response)
+ try:
+ from django.core.wsgi import get_wsgi_application
+ return get_wsgi_application()(environ, start_response)
+ except ImportError:
+ import django.core.handlers.wsgi
+ return django.core.handlers.wsgi.WSGIHandler()(environ, start_response)
diff --git a/schemas/clients.xsd b/schemas/clients.xsd
index 20a77b0dd..b6d04b3bf 100644
--- a/schemas/clients.xsd
+++ b/schemas/clients.xsd
@@ -60,7 +60,7 @@
<xsd:attribute type='xsd:string' name='profile' use='required'>
<xsd:annotation>
<xsd:documentation>
- Profile group naem to associate this client with.
+ Profile group name to associate this client with.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
diff --git a/schemas/packages.xsd b/schemas/packages.xsd
index fc5a1356c..b61b97dde 100644
--- a/schemas/packages.xsd
+++ b/schemas/packages.xsd
@@ -18,6 +18,9 @@
<xsd:enumeration value="apt"/>
<xsd:enumeration value="pac"/>
<xsd:enumeration value="pkgng"/>
+ <xsd:enumeration value="portage"/>
+ <xsd:enumeration value="layman"/>
+ <xsd:enumeration value="dummy"/>
</xsd:restriction>
</xsd:simpleType>
@@ -225,6 +228,25 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
+ <xsd:attribute type="xsd:integer" name="priority">
+ <xsd:annotation>
+ <xsd:documentation>
+ The priority of the source. This is used to order the
+ sources. After sorting, the first source, that could
+ deliver the package, is used. If not supplied the default
+ priority is 500.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute type="xsd:string" name="pin">
+ <xsd:annotation>
+ <xsd:documentation>
+ Extra information for pinning. This information is used
+ to differ between the sources. Should be used in the
+ supported format of apt.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
<xsd:attributeGroup ref="py:genshiAttrs"/>
</xsd:complexType>
diff --git a/schemas/pkgtype.xsd b/schemas/pkgtype.xsd
index 7ad7606b2..993114a46 100644
--- a/schemas/pkgtype.xsd
+++ b/schemas/pkgtype.xsd
@@ -31,10 +31,9 @@
<xsd:annotation>
<xsd:documentation>
Install the named package group. Package groups are only
- supported for Yum :xml:element:`Source` repositories, and
- only if the :ref:`yum libraries
- &lt;native-yum-libraries&gt;` are in use. Either ``group``
- or :xml:attribute:`PackageStructure:name` must be specified.
+ supported for Pac and Yum :xml:element:`Source`
+ repositories. Either ``group`` or
+ :xml:attribute:`PackageStructure:name` must be specified.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
diff --git a/schemas/pkgvars.xsd b/schemas/pkgvars.xsd
new file mode 100644
index 000000000..dbd02726d
--- /dev/null
+++ b/schemas/pkgvars.xsd
@@ -0,0 +1,43 @@
+<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'
+ xmlns:py="http://genshi.edgewall.org/">
+
+ <xsd:annotation>
+ <xsd:documentation>
+ XML-Schema-Definition für PkgVars/*.xml
+ Alexander Sulfrian
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:import namespace="http://genshi.edgewall.org/"
+ schemaLocation="genshi.xsd"/>
+
+ <xsd:complexType name='pkgVarType'>
+ <xsd:attribute type='xsd:string' name='name'/>
+
+ <xsd:attribute type='xsd:string' name='pin'/>
+ <xsd:attribute type='xsd:string' name='use'/>
+ <xsd:attribute type='xsd:string' name='keywords'/>
+
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:complexType name='containerType'>
+ <xsd:choice maxOccurs='unbounded'>
+ <xsd:element name='Package' type='pkgVarType'/>
+ <xsd:element name='Client' type='containerType'/>
+ <xsd:element name='Group' type='containerType'/>
+ </xsd:choice>
+ <xsd:attribute name='name' type='xsd:string' use='required'/>
+ <xsd:attribute name='negate' type='xsd:boolean'/>
+ </xsd:complexType>
+
+ <xsd:complexType name='pkgVarsType'>
+ <xsd:choice minOccurs='0' maxOccurs='unbounded'>
+ <xsd:element name='Package' type='pkgVarType'/>
+ <xsd:element name='Client' type='containerType'/>
+ <xsd:element name='Group' type='containerType'/>
+ </xsd:choice>
+ </xsd:complexType>
+
+ <xsd:element name='PkgVars' type='pkgVarsType'/>
+</xsd:schema>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index 0a55f6355..5165d186b 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -28,6 +28,7 @@
<xsd:enumeration value='ebuild' />
<xsd:enumeration value='yum' />
<xsd:enumeration value='freebsdpkg' />
+ <xsd:enumeration value='dummy' />
</xsd:restriction>
</xsd:simpleType>
diff --git a/setup.py b/setup.py
index dc998d872..5ff2ac003 100755
--- a/setup.py
+++ b/setup.py
@@ -16,11 +16,12 @@ inst_reqs = [
'lockfile',
'lxml',
'python-daemon',
+ 'argparse'
]
-# we only need m2crypto on < python2.6
+# Use the backported ssl module on < python2.6
if sys.version_info[:2] < (2, 6):
- inst_reqs.append('M2Crypto')
+ inst_reqs.append('ssl')
setup(name="Bcfg2",
version=__version__, # Defined in src/lib/Bcfg2/version.py
@@ -42,6 +43,7 @@ setup(name="Bcfg2",
'Bcfg2.Server',
"Bcfg2.Server.FileMonitor",
"Bcfg2.Server.Lint",
+ "Bcfg2.Server.migrations",
"Bcfg2.Server.Plugin",
"Bcfg2.Server.Plugins",
"Bcfg2.Server.Plugins.Packages",
diff --git a/solaris-ips/MANIFEST.bcfg2-server.header b/solaris-ips/MANIFEST.bcfg2-server.header
index 5a837b5c7..e6b8abaf3 100644
--- a/solaris-ips/MANIFEST.bcfg2-server.header
+++ b/solaris-ips/MANIFEST.bcfg2-server.header
@@ -1,4 +1,4 @@
license ../../LICENSE license=simplified_bsd
set name=description value="Configuration management server"
set name=pkg.summary value="Configuration management server"
-set name=pkg.fmri value="pkg://bcfg2/bcfg2-server@1.4.0pre1"
+set name=pkg.fmri value="pkg://bcfg2/bcfg2-server@1.4.0pre2"
diff --git a/solaris-ips/MANIFEST.bcfg2.header b/solaris-ips/MANIFEST.bcfg2.header
index 5fd5c0c5d..6ec6642af 100644
--- a/solaris-ips/MANIFEST.bcfg2.header
+++ b/solaris-ips/MANIFEST.bcfg2.header
@@ -1,5 +1,5 @@
license ../../LICENSE license=simplified_bsd
set name=description value="Configuration management client"
set name=pkg.summary value="Configuration management client"
-set name=pkg.fmri value="pkg://bcfg2/bcfg2@1.4.0pre1"
+set name=pkg.fmri value="pkg://bcfg2/bcfg2@1.4.0pre2"
file usr/bin/bcfg2 group=bin mode=0755 owner=root path=usr/bin/bcfg2
diff --git a/solaris-ips/Makefile b/solaris-ips/Makefile
index 63a1ac61e..b280721dc 100644
--- a/solaris-ips/Makefile
+++ b/solaris-ips/Makefile
@@ -1,6 +1,6 @@
#!/usr/bin/gmake
-VERS=1.4.0pre1-1
+VERS=1.4.0pre2-1
PYVERSION := $(shell python -c "import sys; print sys.version[0:3]")
default: clean package
diff --git a/solaris-ips/pkginfo.bcfg2 b/solaris-ips/pkginfo.bcfg2
index 236009c8f..35a3be68a 100644
--- a/solaris-ips/pkginfo.bcfg2
+++ b/solaris-ips/pkginfo.bcfg2
@@ -1,7 +1,7 @@
PKG="SCbcfg2"
NAME="bcfg2"
ARCH="sparc"
-VERSION="1.4.0pre1"
+VERSION="1.4.0pre2"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/solaris-ips/pkginfo.bcfg2-server b/solaris-ips/pkginfo.bcfg2-server
index af1ab36fc..e2eb91174 100644
--- a/solaris-ips/pkginfo.bcfg2-server
+++ b/solaris-ips/pkginfo.bcfg2-server
@@ -1,7 +1,7 @@
PKG="SCbcfg2-server"
NAME="bcfg2-server"
ARCH="sparc"
-VERSION="1.4.0pre1"
+VERSION="1.4.0pre2"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/solaris/Makefile b/solaris/Makefile
index 38cf2547d..bac597afa 100644
--- a/solaris/Makefile
+++ b/solaris/Makefile
@@ -1,7 +1,7 @@
#!/usr/sfw/bin/gmake
PYTHON="/usr/local/bin/python"
-VERS=1.4.0pre1-1
+VERS=1.4.0pre2-1
PYVERSION := $(shell $(PYTHON) -c "import sys; print sys.version[0:3]")
default: clean package
diff --git a/solaris/pkginfo.bcfg2 b/solaris/pkginfo.bcfg2
index 236009c8f..35a3be68a 100644
--- a/solaris/pkginfo.bcfg2
+++ b/solaris/pkginfo.bcfg2
@@ -1,7 +1,7 @@
PKG="SCbcfg2"
NAME="bcfg2"
ARCH="sparc"
-VERSION="1.4.0pre1"
+VERSION="1.4.0pre2"
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 af1ab36fc..e2eb91174 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.4.0pre1"
+VERSION="1.4.0pre2"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/src/lib/Bcfg2/Client/Proxy.py b/src/lib/Bcfg2/Client/Proxy.py
index 679b4c52b..f383911a3 100644
--- a/src/lib/Bcfg2/Client/Proxy.py
+++ b/src/lib/Bcfg2/Client/Proxy.py
@@ -1,3 +1,4 @@
+import os.path
import re
import sys
import time
@@ -202,6 +203,8 @@ class SSLHTTPConnection(httplib.HTTPConnection):
raise Exception("unknown protocol %s" % self.protocol)
if self.ca:
other_side_required = ssl.CERT_REQUIRED
+ if not os.path.isfile(self.ca):
+ self.logger.error("CA specified but none found at %s" % self.ca)
else:
other_side_required = ssl.CERT_NONE
self.logger.warning("No ca is specified. Cannot authenticate the "
@@ -316,6 +319,7 @@ class ComponentProxy(xmlrpclib.ServerProxy):
help='The time in seconds to wait between retries'),
Bcfg2.Options.Option(
'--ssl-cns', cf=('communication', 'serverCommonNames'),
+ dest="ssl_cns",
type=Bcfg2.Options.Types.colon_list,
help='List of server commonNames')]
diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py
index 5a86e8cd4..77610d9bc 100644
--- a/src/lib/Bcfg2/Client/Tools/APT.py
+++ b/src/lib/Bcfg2/Client/Tools/APT.py
@@ -35,6 +35,7 @@ class APT(Bcfg2.Client.Tools.Tool):
self.debsums = '%s/bin/debsums' % Bcfg2.Options.setup.apt_install_path
self.aptget = '%s/bin/apt-get' % Bcfg2.Options.setup.apt_install_path
self.dpkg = '%s/bin/dpkg' % Bcfg2.Options.setup.apt_install_path
+ self.aptmark = '%s/bin/apt-mark' % Bcfg2.Options.setup.apt_install_path
self.__execs__ = [self.debsums, self.aptget, self.dpkg]
path_entries = os.environ['PATH'].split(':')
@@ -42,10 +43,11 @@ class APT(Bcfg2.Client.Tools.Tool):
if reqdir not in path_entries:
os.environ['PATH'] = os.environ['PATH'] + ':' + reqdir
self.pkgcmd = '%s ' % self.aptget + \
- '-o DPkg::Options::=--force-confold ' + \
- '-o DPkg::Options::=--force-confmiss ' + \
- '--reinstall ' + \
- '--force-yes '
+ '-o DPkg::Options::=--force-confold ' + \
+ '-o DPkg::Options::=--force-confmiss ' + \
+ '--reinstall ' + \
+ '--no-install-recommends ' + \
+ '--force-yes '
if not Bcfg2.Options.setup.debug:
self.pkgcmd += '-q=2 '
self.pkgcmd += '-y install %s'
@@ -68,8 +70,8 @@ class APT(Bcfg2.Client.Tools.Tool):
Bcfg2.Options.setup.apt_etc_path))]
self.nonexistent = [entry.get('name') for struct in config
for entry in struct
- if entry.tag == 'Path' and
- entry.get('type') == 'nonexistent']
+ if (entry.tag == 'Path' and
+ entry.get('type') == 'nonexistent')]
os.environ["DEBIAN_FRONTEND"] = 'noninteractive'
self.actions = {}
if Bcfg2.Options.setup.kevlar and not Bcfg2.Options.setup.dry_run:
@@ -87,6 +89,23 @@ class APT(Bcfg2.Client.Tools.Tool):
except apt.cache.FetchFailedException:
err = sys.exc_info()[1]
self.logger.info("Failed to update APT cache: %s" % err)
+ # mark dependencies as being automatically installed and vice versa
+ mark = []
+ unmark = []
+ try:
+ installed_pkgs = [p.name for p in self.pkg_cache if p.is_installed]
+ except AttributeError:
+ installed_pkgs = [p.name for p in self.pkg_cache if p.isInstalled]
+ for pkg in self.getSupportedEntries():
+ if pkg.get('name') in installed_pkgs:
+ if pkg.get('origin') == 'Packages':
+ mark.append(pkg.get('name'))
+ else:
+ unmark.append(pkg.get('name'))
+ if mark:
+ self.cmd.run("%s markauto %s" % (self.aptmark, (" ".join(mark))))
+ if unmark:
+ self.cmd.run("%s unmarkauto %s" % (self.aptmark, (" ".join(unmark))))
self.pkg_cache = apt.cache.Cache()
def FindExtra(self):
@@ -158,21 +177,23 @@ class APT(Bcfg2.Client.Tools.Tool):
(entry.attrib['name']))
return False
pkgname = entry.get('name')
- if pkgname not in self.pkg_cache or \
- not self.pkg_cache[pkgname].is_installed:
+ if (pkgname not in self.pkg_cache or
+ not self.pkg_cache[pkgname].is_installed):
self.logger.info("Package %s not installed" % (entry.get('name')))
entry.set('current_exists', 'false')
return False
pkg = self.pkg_cache[pkgname]
installed_version = pkg.installed.version
- if entry.get('version') == 'auto':
+ if entry.get('version').startswith('auto'):
if pkg.is_upgradable:
desired_version = pkg.candidate.version
else:
desired_version = installed_version
- elif entry.get('version') == 'any':
+ entry.set('version', "auto: %s" % desired_version)
+ elif entry.get('version').startswith('any'):
desired_version = installed_version
+ entry.set('version', "any: %s" % desired_version)
else:
desired_version = entry.get('version')
if desired_version != installed_version:
@@ -183,9 +204,9 @@ class APT(Bcfg2.Client.Tools.Tool):
return False
else:
# version matches
- if not Bcfg2.Options.setup.quick \
- and entry.get('verify', 'true') == 'true' \
- and checksums:
+ if (not Bcfg2.Options.setup.quick and
+ entry.get('verify', 'true') == 'true' and
+ checksums):
pkgsums = self.VerifyDebsums(entry, modlist)
return pkgsums
return True
@@ -215,7 +236,7 @@ class APT(Bcfg2.Client.Tools.Tool):
self.logger.error("APT has no information about package %s"
% pkgname)
continue
- if pkg.get('version') in ['auto', 'any']:
+ if any([pkg.get('version').startswith(v) for v in ['auto', 'any']]):
try:
ipkgs.append("%s=%s" % (
pkgname,
@@ -236,16 +257,21 @@ class APT(Bcfg2.Client.Tools.Tool):
self.logger.error("Cannot find correct versions of packages:")
self.logger.error(bad_pkgs)
if not ipkgs:
- return
+ return dict()
if not self.cmd.run(self.pkgcmd % (" ".join(ipkgs))):
self.logger.error("APT command failed")
self.pkg_cache = apt.cache.Cache()
self.extra = self.FindExtra()
+ mark = []
states = dict()
for package in packages:
states[package] = self.VerifyPackage(package, [], checksums=False)
if states[package]:
self.modified.append(package)
+ if package.get('origin') == 'Packages':
+ mark.append(package.get('name'))
+ if mark:
+ self.cmd.run("%s markauto %s" % (self.aptmark, (" ".join(mark))))
return states
def VerifyPath(self, entry, _): # pylint: disable=W0613
diff --git a/src/lib/Bcfg2/Client/Tools/BundleDeps.py b/src/lib/Bcfg2/Client/Tools/BundleDeps.py
index aaa090633..c1af3f7f1 100644
--- a/src/lib/Bcfg2/Client/Tools/BundleDeps.py
+++ b/src/lib/Bcfg2/Client/Tools/BundleDeps.py
@@ -28,7 +28,7 @@ class BundleDeps(Bcfg2.Client.Tools.Tool):
bundle_name = entry.get('name')
for bundle in self.config.findall('./Bundle/Bundle'):
- if bundle.get('name') == bundle_name and \
- bundle not in self.modified:
+ if (bundle.get('name') == bundle_name and
+ bundle not in self.modified):
self.modified.append(bundle)
return dict()
diff --git a/src/lib/Bcfg2/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
index fab142a7c..b1abb376a 100644
--- a/src/lib/Bcfg2/Client/Tools/Chkconfig.py
+++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
@@ -88,17 +88,17 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
if bootstatus is not None:
if bootstatus == 'on':
# make sure service is enabled on boot
- bootcmd = '/sbin/chkconfig %s %s' % \
- (entry.get('name'), bootstatus)
+ bootcmd = ('/sbin/chkconfig %s %s' %
+ (entry.get('name'), bootstatus))
elif bootstatus == 'off':
# make sure service is disabled on boot
bootcmd = '/sbin/chkconfig %s %s' % (entry.get('name'),
bootstatus)
bootcmdrv = self.cmd.run(bootcmd).success
- if Bcfg2.Options.setup.servicemode == 'disabled':
+ if Bcfg2.Options.setup.service_mode == 'disabled':
# 'disabled' means we don't attempt to modify running svcs
return bootcmdrv
- buildmode = Bcfg2.Options.setup.servicemode == 'build'
+ buildmode = Bcfg2.Options.setup.service_mode == 'build'
if ((entry.get('status') == 'on' and not buildmode) and
entry.get('current_status') == 'off'):
svccmdrv = self.start_service(entry)
diff --git a/src/lib/Bcfg2/Client/Tools/DebInit.py b/src/lib/Bcfg2/Client/Tools/DebInit.py
index 53e5e7ec6..35768f0fe 100644
--- a/src/lib/Bcfg2/Client/Tools/DebInit.py
+++ b/src/lib/Bcfg2/Client/Tools/DebInit.py
@@ -142,8 +142,8 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
# 'disabled' means we don't attempt to modify running svcs
return bootcmdrv and seqcmdrv
buildmode = Bcfg2.Options.setup.service_mode == 'build'
- if (entry.get('status') == 'on' and not buildmode) and \
- entry.get('current_status') == 'off':
+ if ((entry.get('status') == 'on' and not buildmode) and
+ entry.get('current_status') == 'off'):
svccmdrv = self.start_service(entry)
elif (entry.get('status') == 'off' or buildmode) and \
entry.get('current_status') == 'on':
diff --git a/src/lib/Bcfg2/Client/Tools/Dummy.py b/src/lib/Bcfg2/Client/Tools/Dummy.py
new file mode 100644
index 000000000..9a96eb904
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/Dummy.py
@@ -0,0 +1,16 @@
+"""This is the Bcfg2 tool for the Dummy package system."""
+
+import re
+import Bcfg2.Client.Tools
+
+
+class Dummy(Bcfg2.Client.Tools.PkgTool):
+ __handles__ = [('Package', 'dummy')]
+ __req__ = {'Package': []}
+ pkgtype = 'dummy'
+
+ def RefreshPackages(self):
+ pass
+
+ def VerifyPackage(self, _entry, _):
+ return True
diff --git a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
index 24bc4cf36..7c25e6804 100644
--- a/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
+++ b/src/lib/Bcfg2/Client/Tools/FreeBSDInit.py
@@ -42,7 +42,6 @@ class FreeBSDInit(Bcfg2.Client.Tools.SvcTool):
self.logger.debug('Stopping service %s' % service.get('name'))
return self.cmd.run(self.get_svc_command(service, 'onestop'))
-
def VerifyService(self, entry, _):
"""Verify Service status for entry."""
entry.set('target_status', entry.get('status')) # for reporting
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
index fc4e16904..bcd695058 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
@@ -5,15 +5,16 @@ import Bcfg2.Client.XML
from augeas import Augeas
from Bcfg2.Client.Tools.POSIX.base import POSIXTool
from Bcfg2.Client.Tools.POSIX.File import POSIXFile
+from Bcfg2.Compat import all # pylint: disable=W0622
class AugeasCommand(object):
""" Base class for all Augeas command objects """
- def __init__(self, command, augeas_obj, logger):
+ def __init__(self, entry, command, augeas_obj, logger):
self._augeas = augeas_obj
self.command = command
- self.entry = self.command.getparent()
+ self.entry = entry
self.logger = logger
def get_path(self, attr="path"):
@@ -115,8 +116,8 @@ class Remove(AugeasCommand):
class Move(AugeasCommand):
""" Augeas ``move`` command """
- def __init__(self, command, augeas_obj, logger):
- AugeasCommand.__init__(self, command, augeas_obj, logger)
+ def __init__(self, entry, command, augeas_obj, logger):
+ AugeasCommand.__init__(self, entry, command, augeas_obj, logger)
self.source = self.get_path("source")
self.dest = self.get_path("destination")
@@ -131,8 +132,8 @@ class Move(AugeasCommand):
class Set(AugeasCommand):
""" Augeas ``set`` command """
- def __init__(self, command, augeas_obj, logger):
- AugeasCommand.__init__(self, command, augeas_obj, logger)
+ def __init__(self, entry, command, augeas_obj, logger):
+ AugeasCommand.__init__(self, entry, command, augeas_obj, logger)
self.value = self.command.get("value")
def verify(self):
@@ -146,15 +147,15 @@ class Set(AugeasCommand):
class Clear(Set):
""" Augeas ``clear`` command """
- def __init__(self, command, augeas_obj, logger):
- Set.__init__(self, command, augeas_obj, logger)
+ def __init__(self, entry, command, augeas_obj, logger):
+ Set.__init__(self, entry, command, augeas_obj, logger)
self.value = None
class SetMulti(AugeasCommand):
""" Augeas ``setm`` command """
- def __init__(self, command, augeas_obj, logger):
- AugeasCommand.__init__(self, command, augeas_obj, logger)
+ def __init__(self, entry, command, augeas_obj, logger):
+ AugeasCommand.__init__(self, entry, command, augeas_obj, logger)
self.sub = self.command.get("sub")
self.value = self.command.get("value")
self.base = self.get_path("base")
@@ -170,8 +171,8 @@ class SetMulti(AugeasCommand):
class Insert(AugeasCommand):
""" Augeas ``ins`` command """
- def __init__(self, command, augeas_obj, logger):
- AugeasCommand.__init__(self, command, augeas_obj, logger)
+ def __init__(self, entry, command, augeas_obj, logger):
+ AugeasCommand.__init__(self, entry, command, augeas_obj, logger)
self.label = self.command.get("label")
self.where = self.command.get("where", "before")
self.before = self.where == "before"
@@ -230,11 +231,12 @@ class POSIXAugeas(POSIXTool):
objects representing the commands.
"""
rv = []
- for cmd in entry.iterchildren():
+ for cmd in entry:
if cmd.tag == "Initial":
continue
if cmd.tag in globals():
- rv.append(globals()[cmd.tag](cmd, self.get_augeas(entry),
+ rv.append(globals()[cmd.tag](entry, cmd,
+ self.get_augeas(entry),
self.logger))
else:
err = "Augeas: Unknown command %s in %s" % (cmd.tag,
@@ -248,8 +250,8 @@ class POSIXAugeas(POSIXTool):
for cmd in self.get_commands(entry):
try:
if not cmd.verify():
- err = "Augeas: Command has not been applied to %s: %s" % \
- (entry.get("name"), cmd)
+ err = ("Augeas: Command has not been applied to %s: %s" %
+ (entry.get("name"), cmd))
self.logger.debug(err)
entry.set('qtext', "\n".join([entry.get('qtext', ''),
err]))
@@ -258,8 +260,8 @@ class POSIXAugeas(POSIXTool):
else:
cmd.command.set("verified", "true")
except: # pylint: disable=W0702
- err = "Augeas: Unexpected error verifying %s: %s: %s" % \
- (entry.get("name"), cmd, sys.exc_info()[1])
+ err = ("Augeas: Unexpected error verifying %s: %s: %s" %
+ (entry.get("name"), cmd, sys.exc_info()[1]))
self.logger.error(err)
entry.set('qtext', "\n".join([entry.get('qtext', ''), err]))
rv = False
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Device.py b/src/lib/Bcfg2/Client/Tools/POSIX/Device.py
index 6237ccce2..e90ecd384 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/Device.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Device.py
@@ -1,4 +1,4 @@
-""" Handle <Path type='nonexistent' ...> entries """
+""" Handle <Path type='device' ...> entries """
import os
import sys
@@ -6,7 +6,7 @@ from Bcfg2.Client.Tools.POSIX.base import POSIXTool, device_map
class POSIXDevice(POSIXTool):
- """ Handle <Path type='nonexistent' ...> entries """
+ """ Handle <Path type='device' ...> entries """
__req__ = ['name', 'dev_type', 'mode', 'owner', 'group']
def fully_specified(self, entry):
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/File.py b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
index fc445e07c..1f1772d46 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/File.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
@@ -8,6 +8,7 @@ import tempfile
import Bcfg2.Options
from Bcfg2.Client.Tools.POSIX.base import POSIXTool
from Bcfg2.Compat import unicode, b64encode, b64decode # pylint: disable=W0622
+import Bcfg2.Utils
class POSIXFile(POSIXTool):
@@ -17,21 +18,6 @@ class POSIXFile(POSIXTool):
def fully_specified(self, entry):
return entry.text is not None or entry.get('empty', 'false') == 'true'
- def _is_string(self, strng, encoding):
- """ Returns true if the string contains no ASCII control
- characters and can be decoded from the specified encoding. """
- for char in strng:
- if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
- return False
- if not hasattr(strng, "decode"):
- # py3k
- return True
- try:
- strng.decode(encoding)
- return True
- except: # pylint: disable=W0702
- return False
-
def _get_data(self, entry):
""" Get a tuple of (<file data>, <is binary>) for the given entry """
is_binary = entry.get('encoding', 'ascii') == 'base64'
@@ -181,8 +167,8 @@ class POSIXFile(POSIXTool):
(entry.get("name"), sys.exc_info()[1]))
return False
if not is_binary:
- is_binary |= not self._is_string(content,
- Bcfg2.Options.setup.encoding)
+ is_binary |= not Bcfg2.Utils.is_string(
+ content, Bcfg2.Options.setup.encoding)
if is_binary:
# don't compute diffs if the file is binary
prompt.append('Binary file, no printable diff')
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
index c27c7559d..41bff751d 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
@@ -14,7 +14,7 @@ from Bcfg2.Client.Tools.POSIX.base import POSIXTool
class POSIX(Bcfg2.Client.Tools.Tool):
"""POSIX File support code."""
- options = Bcfg2.Client.Tools.Tool.options + [
+ options = Bcfg2.Client.Tools.Tool.options + POSIXTool.options + [
Bcfg2.Options.PathOption(
cf=('paranoid', 'path'), default='/var/cache/bcfg2',
dest='paranoid_path',
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
index 8895eaae1..ffa527cd6 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/base.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -6,9 +6,11 @@ import pwd
import grp
import stat
import copy
+import errno
import shutil
import Bcfg2.Client.Tools
import Bcfg2.Client.XML
+import Bcfg2.Options
from Bcfg2.Compat import oct_mode
try:
@@ -37,6 +39,22 @@ device_map = dict(block=stat.S_IFBLK, # pylint: disable=C0103
class POSIXTool(Bcfg2.Client.Tools.Tool):
""" Base class for tools that handle POSIX (Path) entries """
+
+ options = [
+ Bcfg2.Options.Option(
+ cf=('POSIX', 'secontext_ignore'),
+ default=['anon_inodefs_t', 'bdev_t', 'binfmt_misc_fs_t',
+ 'capifs_t', 'configfs_t', 'cpusetfs_t', 'ecryptfs_t',
+ 'eventpollfs_t', 'futexfs_t', 'hugetlbfs_t', 'ibmasmfs_t',
+ 'inotifyfs_t', 'mvfs_t', 'nfsd_fs_t', 'oprofilefs_t',
+ 'ramfs_t', 'romfs_t', 'rpc_pipefs_t', 'spufs_t',
+ 'squash_t', 'vmblock_t', 'vxfs_t', 'xenfs_t', 'autofs_t',
+ 'cifs_t', 'dosfs_t', 'fusefs_t', 'iso9660_t',
+ 'removable_t', 'nfs_t'],
+ help='secontext types to ignore labeling errors',
+ type=Bcfg2.Options.Types.colon_list)
+ ]
+
def fully_specified(self, entry): # pylint: disable=W0613
""" return True if the entry is fully specified """
# checking is done by __req__
@@ -112,14 +130,14 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
% (path,
self._norm_entry_uid(entry),
self._norm_entry_gid(entry)))
- os.chown(path, self._norm_entry_uid(entry),
- self._norm_entry_gid(entry))
+ os.lchown(path, self._norm_entry_uid(entry),
+ self._norm_entry_gid(entry))
except (OSError, KeyError):
self.logger.error('POSIX: Failed to change ownership of %s'
% path)
rv = False
if sys.exc_info()[0] == KeyError:
- os.chown(path, 0, 0)
+ os.lchown(path, 0, 0)
else:
self.logger.debug("POSIX: Run as non-root, not setting ownership")
@@ -272,7 +290,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
rv &= self._apply_acl(defacl, path, posix1e.ACL_TYPE_DEFAULT)
return rv
- def _set_secontext(self, entry, path=None):
+ def _set_secontext(self, entry, path=None): # pylint: disable=R0911
""" set the SELinux context of the file on disk according to the
config"""
if not HAS_SELINUX:
@@ -284,25 +302,28 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
if not context:
# no context listed
return True
-
- if context == '__default__':
- try:
+ secontext = selinux.lgetfilecon(path)[1].split(":")[2]
+ if secontext in Bcfg2.Options.setup.secontext_ignore:
+ return True
+ try:
+ if context == '__default__':
selinux.restorecon(path)
- rv = True
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("POSIX: Failed to restore SELinux context "
- "for %s: %s" % (path, err))
- rv = False
- else:
- try:
- rv = selinux.lsetfilecon(path, context) == 0
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("POSIX: Failed to restore SELinux context "
- "for %s: %s" % (path, err))
- rv = False
- return rv
+ return True
+ else:
+ return selinux.lsetfilecon(path, context) == 0
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EOPNOTSUPP:
+ # Operation not supported
+ if context != '__default__':
+ self.logger.debug("POSIX: Failed to set SELinux context "
+ "for %s: %s" % (path, err))
+ return False
+ return True
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to set or restore SELinux "
+ "context for %s: %s" % (path, err))
+ return False
def _norm_gid(self, gid):
""" This takes a group name or gid and returns the
@@ -541,8 +562,8 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
except OSError:
errors.append("%s has no default SELinux context" %
entry.get("name"))
- else:
- wanted_secontext = entry.get("secontext")
+ elif entry.get("secontext"):
+ wanted_secontext = entry.get("secontext").split(":")[2]
if (wanted_secontext and
attrib['current_secontext'] != wanted_secontext):
errors.append("SELinux context for path %s is incorrect. "
diff --git a/src/lib/Bcfg2/Client/Tools/Pacman.py b/src/lib/Bcfg2/Client/Tools/Pacman.py
index b82b905e7..fba946bfb 100644
--- a/src/lib/Bcfg2/Client/Tools/Pacman.py
+++ b/src/lib/Bcfg2/Client/Tools/Pacman.py
@@ -5,7 +5,7 @@ import Bcfg2.Client.Tools
class Pacman(Bcfg2.Client.Tools.PkgTool):
- '''Archlinux package support'''
+ '''Arch Linux package support'''
name = 'Pacman'
__execs__ = ["/usr/bin/pacman"]
__handles__ = [('Package', 'pacman')]
@@ -24,8 +24,8 @@ class Pacman(Bcfg2.Client.Tools.PkgTool):
def VerifyPackage(self, entry, _):
'''Verify Package status for entry'''
- self.logger.info("VerifyPackage: %s : %s" % (entry.get('name'),
- entry.get('version')))
+ self.logger.debug("VerifyPackage: %s : %s" % (entry.get('name'),
+ entry.get('version')))
if 'version' not in entry.attrib:
self.logger.info("Cannot verify unversioned package %s" %
@@ -42,11 +42,10 @@ class Pacman(Bcfg2.Client.Tools.PkgTool):
return True
else:
entry.set('current_version', self.installed[entry.get('name')])
- self.logger.info("attribname: %s" % (entry.attrib['name']))
- self.logger.info("attribname: %s" % (entry.attrib['name']))
+ self.logger.debug("attribname: %s" % (entry.attrib['name']))
return False
entry.set('current_exists', 'false')
- self.logger.info("attribname: %s" % (entry.attrib['name']))
+ self.logger.debug("attribname: %s" % (entry.attrib['name']))
return False
def Remove(self, packages):
diff --git a/src/lib/Bcfg2/Client/Tools/Pkgng.py b/src/lib/Bcfg2/Client/Tools/Pkgng.py
index eef86a131..025bc59be 100644
--- a/src/lib/Bcfg2/Client/Tools/Pkgng.py
+++ b/src/lib/Bcfg2/Client/Tools/Pkgng.py
@@ -205,7 +205,7 @@ class Pkgng(Bcfg2.Client.Tools.Tool):
self.logger.error("Cannot find correct versions of packages:")
self.logger.error(bad_pkgs)
if not ipkgs:
- return
+ return dict()
if not self.cmd.run(self.pkgcmd % (" ".join(ipkgs))):
self.logger.error("pkg command failed")
self._load_pkg_cache()
diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py
index 464b7e389..6b379918a 100644
--- a/src/lib/Bcfg2/Client/Tools/RPM.py
+++ b/src/lib/Bcfg2/Client/Tools/RPM.py
@@ -1185,7 +1185,7 @@ class RPM(Bcfg2.Client.Tools.PkgTool):
self.logger.debug('%s: pkg_verify = %s' %
(self.name, Bcfg2.Options.setup.rpm_pkg_verify))
self.logger.debug('%s: install_missing = %s' %
- (self.name, Bcfg2.Options.setup.install_missing))
+ (self.name, Bcfg2.Options.setup.rpm_install_missing))
self.logger.debug('%s: fix_version = %s' %
(self.name, Bcfg2.Options.setup.rpm_fix_version))
self.logger.debug('%s: reinstall_broken = %s' %
diff --git a/src/lib/Bcfg2/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
index a482dbc00..21257f64b 100644
--- a/src/lib/Bcfg2/Client/Tools/RcUpdate.py
+++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
@@ -102,8 +102,8 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
# 'disabled' means we don't attempt to modify running svcs
return bootcmdrv
buildmode = Bcfg2.Options.setup.service_mode == 'build'
- if (entry.get('status') == 'on' and not buildmode) and \
- entry.get('current_status') == 'off':
+ if ((entry.get('status') == 'on' and not buildmode) and
+ entry.get('current_status') == 'off'):
svccmdrv = self.start_service(entry)
elif (entry.get('status') == 'off' or buildmode) and \
entry.get('current_status') == 'on':
diff --git a/src/lib/Bcfg2/Client/Tools/SYSV.py b/src/lib/Bcfg2/Client/Tools/SYSV.py
index 332638de4..4eea0273f 100644
--- a/src/lib/Bcfg2/Client/Tools/SYSV.py
+++ b/src/lib/Bcfg2/Client/Tools/SYSV.py
@@ -119,8 +119,8 @@ class SYSV(Bcfg2.Client.Tools.PkgTool):
self.logger.debug("Package %s not installed" %
entry.get("name"))
else:
- if Bcfg2.Options.setup.quick or \
- entry.attrib.get('verify', 'true') == 'false':
+ if (Bcfg2.Options.setup.quick or
+ entry.attrib.get('verify', 'true') == 'false'):
return True
rv = self.cmd.run("/usr/sbin/pkgchk -n %s" % entry.get('name'))
if rv.success:
diff --git a/src/lib/Bcfg2/Client/Tools/Systemd.py b/src/lib/Bcfg2/Client/Tools/Systemd.py
index f7e5b1b0b..8919e777b 100644
--- a/src/lib/Bcfg2/Client/Tools/Systemd.py
+++ b/src/lib/Bcfg2/Client/Tools/Systemd.py
@@ -2,6 +2,8 @@
"""This is systemd support."""
+import glob
+import os
import Bcfg2.Client.Tools
import Bcfg2.Client.XML
@@ -72,22 +74,34 @@ class Systemd(Bcfg2.Client.Tools.SvcTool):
# Return failure immediately and do not start/stop the service.
return False
- # Start or stop the service, depending on the current servicemode
+ # Start or stop the service, depending on the current service_mode
cmd = None
- if Bcfg2.Options.setup.servicemode == 'disabled':
+ if Bcfg2.Options.setup.service_mode == 'disabled':
# 'disabled' means we don't attempt to modify running svcs
pass
- elif Bcfg2.Options.setup.servicemode == 'build':
+ elif Bcfg2.Options.setup.service_mode == 'build':
# 'build' means we attempt to stop all services started
if entry.get('current_status') == 'on':
cmd = self.get_svc_command(entry, 'stop')
else:
if entry.get('status') == 'on':
cmd = self.get_svc_command(entry, 'start')
- else:
+ elif entry.get('status') == 'off':
cmd = self.get_svc_command(entry, 'stop')
if cmd:
return self.cmd.run(cmd).success
else:
return True
+
+ def FindExtra(self):
+ """Find Extra Systemd Service entries."""
+ specified = [self.get_svc_name(entry)
+ for entry in self.getSupportedEntries()]
+ extra = set()
+ for fname in glob.glob("/etc/systemd/system/*.wants/*"):
+ name = os.path.basename(fname)
+ if name not in specified:
+ extra.add(name)
+ return [Bcfg2.Client.XML.Element('Service', name=name, type='systemd')
+ for name in list(extra)]
diff --git a/src/lib/Bcfg2/Client/XML.py b/src/lib/Bcfg2/Client/XML.py
index 4ba06abae..93e4facdb 100644
--- a/src/lib/Bcfg2/Client/XML.py
+++ b/src/lib/Bcfg2/Client/XML.py
@@ -2,7 +2,7 @@
# library will use lxml, then builtin xml.etree, then ElementTree
-# pylint: disable=E0611,W0611,W0613,C0103
+# pylint: disable=E0611,E1101,W0611,W0613,C0103
try:
from lxml.etree import Element, SubElement, tostring, XMLParser
diff --git a/src/lib/Bcfg2/Client/__init__.py b/src/lib/Bcfg2/Client/__init__.py
index 674162572..157cc7f65 100644
--- a/src/lib/Bcfg2/Client/__init__.py
+++ b/src/lib/Bcfg2/Client/__init__.py
@@ -10,6 +10,7 @@ import fnmatch
import logging
import argparse
import tempfile
+import copy
import Bcfg2.Logger
import Bcfg2.Options
from Bcfg2.Client import XML
@@ -67,7 +68,7 @@ def prompt(msg):
ans = safe_input(msg)
return ans in ['y', 'Y']
except UnicodeEncodeError:
- ans = input(msg.encode('utf-8'))
+ ans = safe_input(msg.encode('utf-8'))
return ans in ['y', 'Y']
except (EOFError, KeyboardInterrupt):
# handle ^C
@@ -920,8 +921,8 @@ class Client(object):
"""Generate XML summary of execution statistics."""
states = {}
for (item, val) in list(self.states.items()):
- if not Bcfg2.Options.setup.only_important or \
- item.get('important', 'false').lower() == 'true':
+ if (not Bcfg2.Options.setup.only_important or
+ item.get('important', 'false').lower() == 'true'):
states[item] = val
feedback = XML.Element("upload-statistics")
@@ -929,6 +930,11 @@ class Client(object):
'Statistics', total=str(len(states)),
version='2.0',
revision=self.config.get('revision', '-1'))
+ flags = XML.SubElement(stats, "Flags")
+ XML.SubElement(flags, "Flag", name="dry_run",
+ value=str(Bcfg2.Options.setup.dry_run))
+ XML.SubElement(flags, "Flag", name="only_important",
+ value=str(Bcfg2.Options.setup.only_important))
good_entries = [key for key, val in list(states.items()) if val]
good = len(good_entries)
stats.set('good', str(good))
@@ -945,9 +951,10 @@ class Client(object):
if not states[entry]], "Bad")]:
container = XML.SubElement(stats, ename)
for item in data:
- item.set('qtext', '')
- container.append(item)
- item.text = None
+ new_item = copy.deepcopy(item)
+ new_item.set('qtext', '')
+ container.append(new_item)
+ new_item.text = None
timeinfo = XML.Element("OpStamps")
feedback.append(stats)
diff --git a/src/lib/Bcfg2/Compat.py b/src/lib/Bcfg2/Compat.py
index b8a75a0c5..1c2420ccf 100644
--- a/src/lib/Bcfg2/Compat.py
+++ b/src/lib/Bcfg2/Compat.py
@@ -286,3 +286,9 @@ except NameError:
def cmp(a, b):
""" Py3k implementation of cmp() """
return (a > b) - (a < b)
+
+# ast was introduced in python 2.6
+try:
+ from ast import literal_eval
+except ImportError:
+ literal_eval = eval
diff --git a/src/lib/Bcfg2/DBSettings.py b/src/lib/Bcfg2/DBSettings.py
index 5a83c25c3..5d99d4ee2 100644
--- a/src/lib/Bcfg2/DBSettings.py
+++ b/src/lib/Bcfg2/DBSettings.py
@@ -21,7 +21,8 @@ try:
except ImportError:
HAS_SOUTH = False
-settings = dict( # pylint: disable=C0103
+# pylint: disable=C0103
+settings = dict(
TIME_ZONE=None,
TEMPLATE_DEBUG=False,
DEBUG=False,
@@ -50,37 +51,66 @@ settings = dict( # pylint: disable=C0103
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.middleware.doc.XViewMiddleware'),
+ 'django.contrib.auth.middleware.AuthenticationMiddleware'),
ROOT_URLCONF='Bcfg2.Reporting.urls',
AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.ModelBackend'),
LOGIN_URL='/login',
SESSION_EXPIRE_AT_BROWSER_CLOSE=True,
- TEMPLATE_DIRS=(
- '/usr/share/python-support/python-django/django/contrib/admin/'
- 'templates/'),
- TEMPLATE_CONTEXT_PROCESSORS=(
+ DATABASE_ROUTERS=['Bcfg2.DBSettings.PerApplicationRouter'],
+ TEST_RUNNER='django.test.simple.DjangoTestSuiteRunner',
+ CACHES={
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ }
+ })
+
+if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] >= 6:
+ settings['MIDDLEWARE_CLASSES'] += \
+ ('django.contrib.admindocs.middleware.XViewMiddleware',)
+elif HAS_SOUTH:
+ settings['MIDDLEWARE_CLASSES'] += \
+ ('django.middleware.doc.XViewMiddleware',)
+
+if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ settings['INSTALLED_APPS'] += ('Bcfg2.Reporting',)
+elif HAS_SOUTH:
+ settings['INSTALLED_APPS'] += ('south', 'Bcfg2.Reporting')
+ settings['SOUTH_MIGRATION_MODULES'] = {
+ 'Reporting': 'Bcfg2.Reporting.south_migrations',
+ 'Server': 'Bcfg2.Server.south_migrations',
+ }
+if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] >= 8:
+ settings['TEMPLATES'] = [{
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [
+ '/usr/share/python-support/python-django/' +
+ 'django/contrib/admin/templates/'
+ ],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.contrib.auth.context_processors.auth',
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.i18n',
+ 'django.template.context_processors.media',
+ 'django.template.context_processors.request',
+ ],
+ },
+ }]
+else:
+ settings['TEMPLATE_DIRS'] = ('/usr/share/python-support/python-django/' +
+ 'django/contrib/admin/templates/')
+ settings['TEMPLATE_CONTEXT_PROCESSORS'] = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
- 'django.core.context_processors.request'),
- DATABASE_ROUTERS=['Bcfg2.DBSettings.PerApplicationRouter'])
+ 'django.core.context_processors.request',
+ )
-if HAS_SOUTH:
- settings['INSTALLED_APPS'] += ('south', 'Bcfg2.Reporting')
if 'BCFG2_LEGACY_MODELS' in os.environ:
settings['INSTALLED_APPS'] += ('Bcfg2.Server.Reports.reports',)
-if HAS_DJANGO and django.VERSION[0] == 1 and django.VERSION[1] < 3:
- settings['CACHE_BACKEND'] = 'locmem:///'
-else:
- settings['CACHES'] = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- }
- }
-
def finalize_django_config(opts=None, silent=False):
""" Perform final Django configuration """
@@ -97,8 +127,8 @@ def finalize_django_config(opts=None, silent=False):
OPTIONS=opts.db_opts,
SCHEMA=opts.db_schema))
- if hasattr(opts, "reporting_db_engine") and \
- opts.reporting_db_engine is not None:
+ if (hasattr(opts, "reporting_db_engine") and
+ opts.reporting_db_engine is not None):
settings['DATABASES']['Reporting'] = dict(
ENGINE="django.db.backends.%s" % opts.reporting_db_engine,
NAME=opts.reporting_db_name,
@@ -123,6 +153,9 @@ def finalize_django_config(opts=None, silent=False):
opts.web_prefix.rstrip('/') + \
settings['MEDIA_URL']
+ if opts.django_settings:
+ settings.update(opts.django_settings)
+
logger = logging.getLogger()
logger.debug("Finalizing Django settings: %s" % settings)
@@ -131,6 +164,8 @@ def finalize_django_config(opts=None, silent=False):
setattr(module, name, value)
try:
django.conf.settings.configure(**settings)
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ django.setup() # pylint: disable=E1101
except RuntimeError:
if not silent:
logger.warning("Failed to finalize Django settings: %s" %
@@ -139,6 +174,10 @@ def finalize_django_config(opts=None, silent=False):
def sync_databases(**kwargs):
""" Synchronize all databases that we know about. """
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ # Nothing needed here, it's all handled with migrate
+ return
+
logger = logging.getLogger()
for database in settings['DATABASES']:
logger.debug("Syncing database %s" % (database))
@@ -146,11 +185,55 @@ def sync_databases(**kwargs):
**kwargs)
+def upgrade_to_django_migrations(database, logger):
+ """
+ Get the migration state from south and move django migrations to
+ the same state by fake applying the same migration.
+
+ Note: We cannot use south directly here, because this functions
+ runs on django-1.7 or higher, that is not supported by south.
+ """
+
+ last_migration = None
+ try:
+ # get latest south migration
+ cursor = django.db.connections[database].cursor()
+ cursor.cursor.execute('SELECT migration FROM south_migrationhistory')
+ applied_migrations = [name for (name,) in cursor.fetchall()]
+ last_migration = sorted(applied_migrations).pop()
+ # django.db.DatabaseError is not working here, because we are
+ # using the low level api to interact directly with the database
+ except: # pylint: disable=W0702
+ logger.debug("No south migration detected for database: %s." %
+ database)
+
+ if last_migration is not None:
+ # fake-apply matching django migrations
+ django.core.management.call_command(
+ "migrate", 'Reporting', last_migration,
+ database=database, fake=True)
+
+
+def initial_django_migration(database):
+ """ Check if we ever executed an initial django migration. """
+ from django.db.migrations import loader # pylint: disable=E0611
+ loader = loader.MigrationLoader(django.db.connections[database])
+ return len(loader.applied_migrations) == 0
+
+
def migrate_databases(**kwargs):
""" Do South migrations on all databases that we know about. """
logger = logging.getLogger()
for database in settings['DATABASES']:
logger.debug("Migrating database %s" % (database))
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ if initial_django_migration(database):
+ logger.warning(
+ "No applied django migrations found for database %s. "
+ "Trying to get the state from south migration in case "
+ "you just upgraded your django version." % database)
+ upgrade_to_django_migrations(database, logger)
+
django.core.management.call_command("migrate", database=database,
**kwargs)
@@ -193,7 +276,14 @@ class PerApplicationRouter(object):
def allow_syncdb(self, *_):
""" Called when Django wants to determine which models to sync to a
given database. Take the cowards way out and sync all models to all
- databases to allow for easy migrations. """
+ databases to allow for easy migrations. This method is replaced with
+ allow_migrate in django 1.7 and higher. """
+ return True
+
+ def allow_migrate(self, *_args, **_kwargs):
+ """ Called when Django wants to determine which migrations should
+ be run on a given database. Take the cowards way out and run all
+ migrations to all databases to allow for easy migrations. """
return True
@@ -229,7 +319,7 @@ class _OptionContainer(object):
dest='db_schema', default='public'),
Bcfg2.Options.Option(
cf=('database', 'options'), help='Database options',
- dest='db_opts', type=Bcfg2.Options.Types.comma_dict,
+ dest='db_opts', type=Bcfg2.Options.Types.literal_dict,
default=dict()),
# reporting database options
Bcfg2.Options.Option(
@@ -258,20 +348,25 @@ class _OptionContainer(object):
Bcfg2.Options.Option(
cf=('database', 'reporting_options'),
help='Reporting database options', dest='reporting_db_opts',
- type=Bcfg2.Options.Types.comma_dict, default=dict()),
+ type=Bcfg2.Options.Types.literal_dict, default=dict()),
# Django options
Bcfg2.Options.Option(
cf=('reporting', 'time_zone'), help='Django timezone'),
Bcfg2.Options.BooleanOption(
cf=('reporting', 'web_debug'), help='Django debug'),
Bcfg2.Options.Option(
- cf=('reporting', 'web_prefix'), help='Web prefix')]
+ cf=('reporting', 'web_prefix'), help='Web prefix'),
+ Bcfg2.Options.Option(
+ cf=('reporting', 'django_settings'),
+ help='Additional django settings',
+ type=Bcfg2.Options.Types.literal_dict, default=dict())]
@staticmethod
def component_parsed_hook(opts):
""" Finalize the Django config after this component's options
are parsed. """
- finalize_django_config(opts=opts)
+ if HAS_DJANGO:
+ finalize_django_config(opts=opts)
@staticmethod
def options_parsed_hook():
@@ -280,6 +375,7 @@ class _OptionContainer(object):
early enough in option parsing to be parsed in the 'early'
phase. Chances are good that things will break if that
happens, but we do our best to be a good citizen. """
- finalize_django_config(silent=True)
+ if HAS_DJANGO:
+ finalize_django_config(silent=True)
Bcfg2.Options.get_parser().add_component(_OptionContainer)
diff --git a/src/lib/Bcfg2/Logger.py b/src/lib/Bcfg2/Logger.py
index 11eaeebd1..a26971df4 100644
--- a/src/lib/Bcfg2/Logger.py
+++ b/src/lib/Bcfg2/Logger.py
@@ -41,6 +41,8 @@ class TermiosFormatter(logging.Formatter):
returns = []
line_len = self.width
if isinstance(record.msg, str):
+ if len(record.args) != 0:
+ record.msg = record.msg % record.args
for line in record.msg.split('\n'):
if len(line) <= line_len:
returns.append(line)
@@ -131,10 +133,11 @@ class FragmentingSysLogHandler(logging.handlers.SysLogHandler):
logging.WARNING),
self.format(reconn)))
self.socket.send(msg)
+
+ # If we still fail then drop it. Running
+ # bcfg2-server as non-root can trigger permission
+ # denied exceptions.
except: # pylint: disable=W0702
- # If we still fail then drop it. Running
- # bcfg2-server as non-root can trigger permission
- # denied exceptions.
pass
diff --git a/src/lib/Bcfg2/Options/Options.py b/src/lib/Bcfg2/Options/Options.py
index 752e01b4e..c85cfd87c 100644
--- a/src/lib/Bcfg2/Options/Options.py
+++ b/src/lib/Bcfg2/Options/Options.py
@@ -363,7 +363,7 @@ class RepositoryMacroOption(Option):
def transform_value(self, value):
"""transform the value after macro expansion.
- this can be overridden to further transform the value set by
+ this can be overridden to further transform the value set by
the user *after* macros are expanded, but before the user's
``type`` function is applied. principally exists for
PathOption to canonicalize the path.
diff --git a/src/lib/Bcfg2/Options/Parser.py b/src/lib/Bcfg2/Options/Parser.py
index d146e3aa2..51e41850c 100644
--- a/src/lib/Bcfg2/Options/Parser.py
+++ b/src/lib/Bcfg2/Options/Parser.py
@@ -12,12 +12,14 @@ __all__ = ["setup", "OptionParserException", "Parser", "get_parser",
"new_parser"]
+# pylint: disable=C0103
#: The repository option. This is specified here (and imported into
#: :module:`Bcfg2.Options.Common`) rather than vice-versa due to
#: circular imports.
-repository = PathOption( # pylint: disable=C0103
+repository = PathOption(
'-Q', '--repository', cf=('server', 'repository'),
- default='var/lib/bcfg2', help="Server repository path")
+ default='/var/lib/bcfg2', help="Server repository path")
+# pylint: enable=C0103
#: A module-level :class:`argparse.Namespace` object that stores all
@@ -141,6 +143,9 @@ class Parser(argparse.ArgumentParser):
self.option_list.extend(option.list_options())
option.add_to_parser(self)
+ for opt in option.list_options():
+ opt.default_from_config(self._cfp)
+ self._defaults_set.append(opt)
def add_component(self, component):
""" Add a component (and all of its options) to the
@@ -299,7 +304,7 @@ class Parser(argparse.ArgumentParser):
# check whether the specified bcfg2.conf exists
if not self.unit_test and not os.path.exists(bootstrap.config):
- self.error("Could not read %s" % bootstrap.config)
+ sys.stderr.write("Could not read %s\n" % bootstrap.config)
self.add_config_file(self.configfile.dest, bootstrap.config,
reparse=False)
diff --git a/src/lib/Bcfg2/Options/Subcommands.py b/src/lib/Bcfg2/Options/Subcommands.py
index 8972bde00..2ba81e18d 100644
--- a/src/lib/Bcfg2/Options/Subcommands.py
+++ b/src/lib/Bcfg2/Options/Subcommands.py
@@ -48,6 +48,14 @@ class Subcommand(object):
#: one, ``bcfg2-admin`` does not.)
interactive = True
+ #: Whether or not to expose this command as command line parameter
+ #: or only in an interactive :class:`cmd.Cmd` shell.
+ only_interactive = False
+
+ #: Additional aliases for the command. The contents of the list gets
+ #: added to the default command name (the lowercased class name)
+ aliases = []
+
_ws_re = re.compile(r'\s+', flags=re.MULTILINE)
def __init__(self):
@@ -82,6 +90,7 @@ class Subcommand(object):
"""
if args is not None:
self.parser.namespace = copy.copy(master_setup)
+ self.parser.parsed = False
alist = shlex.split(args)
try:
setup = self.parser.parse(alist)
@@ -141,7 +150,9 @@ class Help(Subcommand):
self._registry = registry
def run(self, setup):
- commands = self._registry.commands
+ commands = dict((name, cmd)
+ for (name, cmd) in self._registry.commands.items()
+ if not cmd.only_interactive)
if setup.command:
try:
commands[setup.command].parser.print_help()
@@ -207,15 +218,22 @@ class CommandRegistry(object):
else:
cmd_obj = cls_or_obj
cmdcls = cmd_obj.__class__
- name = cmdcls.__name__.lower()
- self.commands[name] = cmd_obj
- # py2.5 can't mix *magic and non-magical keyword args, thus
- # the **dict(...)
- self.subcommand_options.append(
- Subparser(*cmdcls.options, **dict(name=name, help=cmdcls.__doc__)))
- if issubclass(self.__class__, cmd.Cmd) and cmdcls.interactive:
- setattr(self, "do_%s" % name, cmd_obj)
- setattr(self, "help_%s" % name, cmd_obj.parser.print_help)
+ names = [cmdcls.__name__.lower()]
+ if cmdcls.aliases:
+ names.extend(cmdcls.aliases)
+
+ for name in names:
+ self.commands[name] = cmd_obj
+
+ if not cmdcls.only_interactive:
+ # py2.5 can't mix *magic and non-magical keyword args, thus
+ # the **dict(...)
+ self.subcommand_options.append(
+ Subparser(*cmdcls.options, **dict(name=name,
+ help=cmdcls.__doc__)))
+ if issubclass(self.__class__, cmd.Cmd) and cmdcls.interactive:
+ setattr(self, "do_%s" % name, cmd_obj)
+ setattr(self, "help_%s" % name, cmd_obj.parser.print_help)
return cmd_obj
def register_commands(self, candidates, parent=Subcommand):
diff --git a/src/lib/Bcfg2/Options/Types.py b/src/lib/Bcfg2/Options/Types.py
index ac099e135..ad2e04f10 100644
--- a/src/lib/Bcfg2/Options/Types.py
+++ b/src/lib/Bcfg2/Options/Types.py
@@ -5,6 +5,7 @@ import os
import re
import pwd
import grp
+from Bcfg2.Compat import literal_eval
_COMMA_SPLIT_RE = re.compile(r'\s*,\s*')
@@ -32,28 +33,10 @@ def colon_list(value):
return value.split(':')
-def comma_dict(value):
- """ Split an option string on commas, optionally surrounded by
- whitespace, and split the resulting items again on equals signs,
- returning a dict """
- result = dict()
- if value:
- items = comma_list(value)
- for item in items:
- if '=' in item:
- key, value = item.split(r'=', 1)
- if value in ["true", "yes", "on"]:
- result[key] = True
- elif value in ["false", "no", "off"]:
- result[key] = False
- else:
- try:
- result[key] = int(value)
- except ValueError:
- result[key] = value
- else:
- result[item] = True
- return result
+def literal_dict(value):
+ """ literally evaluate the option in order to allow for arbitrarily nested
+ dictionaries """
+ return literal_eval(value)
def anchored_regex_list(value):
diff --git a/src/lib/Bcfg2/Reporting/Collector.py b/src/lib/Bcfg2/Reporting/Collector.py
index 153809a35..f05a25732 100644
--- a/src/lib/Bcfg2/Reporting/Collector.py
+++ b/src/lib/Bcfg2/Reporting/Collector.py
@@ -116,7 +116,7 @@ class ReportingCollector(object):
self.storage.__class__.__name__)
self.storage.validate()
except:
- self.logger.error("Storage backed %s failed to validate: %s" %
+ self.logger.error("Storage backend %s failed to validate: %s" %
(self.storage.__class__.__name__,
sys.exc_info()[1]))
diff --git a/src/lib/Bcfg2/Reporting/Compat.py b/src/lib/Bcfg2/Reporting/Compat.py
index 9113fdb91..a87a8e480 100644
--- a/src/lib/Bcfg2/Reporting/Compat.py
+++ b/src/lib/Bcfg2/Reporting/Compat.py
@@ -13,4 +13,16 @@ try:
from django.conf.urls.defaults import url, patterns
except ImportError:
# Django > 1.6
- from django.conf.urls import url, patterns
+ from django.conf.urls import url
+
+ try:
+ from django.conf.urls import patterns
+ except:
+ # Django > 1.10
+ def patterns(_prefix, *urls):
+ url_list = list()
+ for u in urls:
+ if isinstance(u, (list, tuple)):
+ u = url(*u)
+ url_list.append(u)
+ return url_list
diff --git a/src/lib/Bcfg2/Reporting/Reports.py b/src/lib/Bcfg2/Reporting/Reports.py
index 3b9c83433..e60b2e82e 100755
--- a/src/lib/Bcfg2/Reporting/Reports.py
+++ b/src/lib/Bcfg2/Reporting/Reports.py
@@ -4,60 +4,46 @@
import sys
import argparse
import datetime
+import django
import Bcfg2.DBSettings
+from django.core.exceptions import ObjectDoesNotExist
-def hosts_by_entry_type(clients, etype, entryspec):
- result = []
- for entry in entryspec:
- for client in clients:
- items = getattr(client.current_interaction, etype)()
- for item in items:
- if (item.entry_type == entry[0] and
- item.name == entry[1]):
- result.append(client)
- return result
+def print_entries(interaction, etype):
+ items = getattr(interaction, etype)()
+ for item in items:
+ print("%-70s %s" % (item.entry_type + ":" + item.name, etype))
-def print_fields(fields, client, fmt, extra=None):
- """ Prints the fields specified in fields of client, max_name
- specifies the column width of the name column. """
- fdata = []
- if extra is None:
- extra = dict()
- for field in fields:
- if field == 'time':
- fdata.append(str(client.current_interaction.timestamp))
- elif field == 'state':
- if client.current_interaction.isclean():
- fdata.append("clean")
- else:
- fdata.append("dirty")
- elif field == 'total':
- fdata.append(client.current_interaction.total_count)
- elif field == 'good':
- fdata.append(client.current_interaction.good_count)
- elif field == 'modified':
- fdata.append(client.current_interaction.modified_count)
- elif field == 'extra':
- fdata.append(client.current_interaction.extra_count)
- elif field == 'bad':
- fdata.append((client.current_interaction.bad_count))
- elif field == 'stale':
- fdata.append(client.current_interaction.isstale())
- else:
- try:
- fdata.append(getattr(client, field))
- except AttributeError:
- fdata.append(extra.get(field, "N/A"))
+class _FlagsFilterMixin(object):
+ """ Mixin that allows to filter the interactions based on the
+ only_important and/or the dry_run flag """
- print(fmt % tuple(fdata))
+ options = [
+ Bcfg2.Options.BooleanOption(
+ "-n", "--no-dry-run",
+ help="Do not consider interactions created with the --dry-run "
+ "flag"),
+ Bcfg2.Options.BooleanOption(
+ "-i", "--no-only-important",
+ help="Do not consider interactions created with the "
+ "--only-important flag")]
+ def get_interaction(self, client, setup):
+ if not setup.no_dry_run and not setup.no_only_important:
+ return client.current_interaction
-def print_entries(interaction, etype):
- items = getattr(interaction, etype)()
- for item in items:
- print("%-70s %s" % (item.entry_type + ":" + item.name, etype))
+ filter = {}
+ if setup.no_dry_run:
+ filter['dry_run'] = False
+ if setup.no_only_important:
+ filter['only_important'] = False
+
+ from Bcfg2.Reporting.models import Interaction
+ try:
+ return Interaction.objects.filter(client=client, **filter).latest()
+ except ObjectDoesNotExist:
+ return None
class _SingleHostCmd(Bcfg2.Options.Subcommand): # pylint: disable=W0223
@@ -74,10 +60,10 @@ class _SingleHostCmd(Bcfg2.Options.Subcommand): # pylint: disable=W0223
raise SystemExit(2)
-class Show(_SingleHostCmd):
+class Show(_SingleHostCmd, _FlagsFilterMixin):
""" Show bad, extra, modified, or all entries from a given host """
- options = _SingleHostCmd.options + [
+ options = _SingleHostCmd.options + _FlagsFilterMixin.options + [
Bcfg2.Options.BooleanOption(
"-b", "--bad", help="Show bad entries from HOST"),
Bcfg2.Options.BooleanOption(
@@ -88,22 +74,32 @@ class Show(_SingleHostCmd):
def run(self, setup):
client = self.get_client(setup)
show_all = not setup.bad and not setup.extra and not setup.modified
- if setup.bad or show_all:
- print_entries(client.current_interaction, "bad")
- if setup.modified or show_all:
- print_entries(client.current_interaction, "modified")
- if setup.extra or show_all:
- print_entries(client.current_interaction, "extra")
+ interaction = self.get_interaction(client, setup)
+ if interaction is None:
+ print("No interactions found for host: %s" % client.name)
+ else:
+ if setup.bad or show_all:
+ print_entries(interaction, "bad")
+ if setup.modified or show_all:
+ print_entries(interaction, "modified")
+ if setup.extra or show_all:
+ print_entries(interaction, "extra")
-class Total(_SingleHostCmd):
+class Total(_SingleHostCmd, _FlagsFilterMixin):
""" Show total number of managed and good entries from HOST """
+ options = _SingleHostCmd.options + _FlagsFilterMixin.options
+
def run(self, setup):
client = self.get_client(setup)
- managed = client.current_interaction.total_count
- good = client.current_interaction.good_count
- print("Total managed entries: %d (good: %d)" % (managed, good))
+ interaction = self.get_interaction(client, setup)
+ if interaction is None:
+ print("No interactions found for host: %s" % client.name)
+ else:
+ managed = interaction.total_count
+ good = interaction.good_count
+ print("Total managed entries: %d (good: %d)" % (managed, good))
class Expire(_SingleHostCmd):
@@ -120,9 +116,9 @@ class Expire(_SingleHostCmd):
client.save()
-class _ClientSelectCmd(Bcfg2.Options.Subcommand):
+class _ClientSelectCmd(Bcfg2.Options.Subcommand, _FlagsFilterMixin):
""" Base class for subcommands that display lists of clients """
- options = [
+ options = _FlagsFilterMixin.options + [
Bcfg2.Options.Option("--fields", metavar="FIELD,FIELD,...",
help="Only display the listed fields",
type=Bcfg2.Options.Types.comma_list,
@@ -132,7 +128,42 @@ class _ClientSelectCmd(Bcfg2.Options.Subcommand):
from Bcfg2.Reporting.models import Client
return Client.objects.exclude(current_interaction__isnull=True)
- def display(self, result, fields, extra=None):
+ def _print_fields(self, setup, fields, client, fmt, extra=None):
+ """ Prints the fields specified in fields of client, max_name
+ specifies the column width of the name column. """
+ fdata = []
+ if extra is None:
+ extra = dict()
+ interaction = self.get_interaction(client, setup)
+ for field in fields:
+ if field == 'time':
+ fdata.append(str(interaction.timestamp))
+ elif field == 'state':
+ if interaction.isclean():
+ fdata.append("clean")
+ else:
+ fdata.append("dirty")
+ elif field == 'total':
+ fdata.append(interaction.total_count)
+ elif field == 'good':
+ fdata.append(interaction.good_count)
+ elif field == 'modified':
+ fdata.append(interaction.modified_count)
+ elif field == 'extra':
+ fdata.append(interaction.extra_count)
+ elif field == 'bad':
+ fdata.append(interaction.bad_count)
+ elif field == 'stale':
+ fdata.append(interaction.isstale())
+ else:
+ try:
+ fdata.append(getattr(client, field))
+ except AttributeError:
+ fdata.append(extra.get(field, "N/A"))
+
+ print(fmt % tuple(fdata))
+
+ def display(self, setup, result, fields, extra=None):
if 'name' not in fields:
fields.insert(0, "name")
if not result:
@@ -153,8 +184,8 @@ class _ClientSelectCmd(Bcfg2.Options.Subcommand):
print(fmt % tuple(f.title() for f in fields))
for client in result:
if not client.expiration:
- print_fields(fields, client, fmt,
- extra=extra.get(client, None))
+ self._print_fields(setup, fields, client, fmt,
+ extra=extra.get(client, None))
class Clients(_ClientSelectCmd):
@@ -172,14 +203,17 @@ class Clients(_ClientSelectCmd):
result = []
show_all = not setup.stale and not setup.clean and not setup.dirty
for client in self.get_clients():
- interaction = client.current_interaction
+ interaction = self.get_interaction(client, setup)
+ if interaction is None:
+ continue
+
if (show_all or
(setup.stale and interaction.isstale()) or
(setup.clean and interaction.isclean()) or
(setup.dirty and not interaction.isclean())):
result.append(client)
- self.display(result, setup.fields)
+ self.display(setup, result, setup.fields)
class Entries(_ClientSelectCmd):
@@ -201,6 +235,21 @@ class Entries(_ClientSelectCmd):
Bcfg2.Options.PositionalArgument(
"entries", metavar="TYPE:NAME", nargs="*")]
+ def _hosts_by_entry_type(self, setup, clients, etype, entryspec):
+ result = []
+ for entry in entryspec:
+ for client in clients:
+ interaction = self.get_interaction(client, setup)
+ if interaction is None:
+ continue
+
+ items = getattr(interaction, etype)()
+ for item in items:
+ if (item.entry_type == entry[0] and
+ item.name == entry[1]):
+ result.append(client)
+ return result
+
def run(self, setup):
result = []
if setup.file:
@@ -216,13 +265,15 @@ class Entries(_ClientSelectCmd):
clients = self.get_clients()
if setup.badentry:
- result = hosts_by_entry_type(clients, "bad", entries)
+ result = self._hosts_by_entry_type(setup, clients, "bad", entries)
elif setup.modifiedentry:
- result = hosts_by_entry_type(clients, "modified", entries)
+ result = self._hosts_by_entry_type(setup, clients, "modified",
+ entries)
elif setup.extraentry:
- result = hosts_by_entry_type(clients, "extra", entries)
+ result = self._hosts_by_entry_type(setup, clients, "extra",
+ entries)
- self.display(result, setup.fields)
+ self.display(setup, result, setup.fields)
class Entry(_ClientSelectCmd):
@@ -250,16 +301,19 @@ class Entry(_ClientSelectCmd):
# TODO: batch fetch this. sqlite could break
extra = dict()
for client in self.get_clients():
+ interaction = self.get_interaction(client, setup)
+ if interaction is None:
+ continue
+
ents = entry_cls.objects.filter(
- name=ename,
- interaction=client.current_interaction)
+ name=ename, interaction=interaction)
if len(ents) == 0:
continue
extra[client] = {"entry state": ents[0].get_state_display(),
"reason": ents[0]}
result.append(client)
- self.display(result, fields, extra=extra)
+ self.display(setup, result, fields, extra=extra)
class CLI(Bcfg2.Options.CommandRegistry):
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index efd9e594c..af1b0c341 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -2,26 +2,54 @@
The base for the original DjangoORM (DBStats)
"""
-from lxml import etree
-from datetime import datetime
+import difflib
import traceback
+from datetime import datetime
from time import strptime
-import Bcfg2.Options
-import Bcfg2.DBSettings
-from Bcfg2.Compat import md5
-from Bcfg2.Reporting.Storage.base import StorageBase, StorageError
-from Bcfg2.Server.Plugin.exceptions import PluginExecutionError
-from django.core import management
+from lxml import etree
+import sys
+
+import django
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db.models import FieldDoesNotExist
from django.core.cache import cache
-from django import db
-#Used by GetCurrentEntry
-import difflib
-from Bcfg2.Compat import b64decode
-from Bcfg2.Reporting.models import *
+import Bcfg2.Options
+import Bcfg2.DBSettings
+from Bcfg2.Compat import b64decode, md5
from Bcfg2.Reporting.Compat import transaction
+from Bcfg2.Reporting.Storage.base import StorageBase, StorageError
+from Bcfg2.Server.Plugin.exceptions import PluginExecutionError
+
+
+def load_django_models():
+ """ Load models for Django after option parsing has completed """
+ # pylint: disable=W0602
+ global Interaction, PackageEntry, FilePerms, PathEntry, LinkEntry, \
+ Group, Client, Bundle, TYPE_EXTRA, TYPE_BAD, TYPE_MODIFIED, \
+ FailureEntry, Performance, BaseEntry, ServiceEntry, ActionEntry, \
+ POSIXGroupEntry, POSIXUserEntry, SEBooleanEntry, SEFcontextEntry, \
+ SEInterfaceEntry, SELoginEntry, SEModuleEntry, SENodeEntry, \
+ SEPermissiveEntry, SEPortEntry, SEUserEntry
+ # pylint: enable=W0602
+
+ from Bcfg2.Reporting.models import \
+ Interaction, PackageEntry, FilePerms, PathEntry, LinkEntry, \
+ Group, Client, Bundle, TYPE_EXTRA, TYPE_BAD, TYPE_MODIFIED, \
+ FailureEntry, Performance, BaseEntry, ServiceEntry, ActionEntry, \
+ POSIXGroupEntry, POSIXUserEntry, SEBooleanEntry, SEFcontextEntry, \
+ SEInterfaceEntry, SELoginEntry, SEModuleEntry, SENodeEntry, \
+ SEPermissiveEntry, SEPortEntry, SEUserEntry
+
+
+def get_all_field_names(model):
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 8:
+ return [field.name
+ for field in model._meta.get_fields()
+ if field.auto_created == False and
+ not (field.is_relation and field.related_model is None)]
+ else:
+ return model._meta.get_all_field_names()
class DjangoORM(StorageBase):
@@ -32,6 +60,7 @@ class DjangoORM(StorageBase):
type=Bcfg2.Options.Types.size,
help='Reporting file size limit',
default=1024 * 1024)]
+ options_parsed_hook = staticmethod(load_django_models)
def _import_default(self, entry, state, entrytype=None, defaults=None,
mapping=None, boolean=None, xforms=None):
@@ -80,7 +109,7 @@ class DjangoORM(StorageBase):
for attr in boolean + ["current_exists"]:
xforms[attr] = boolean_xform
act_dict = dict(state=state)
- for fieldname in entrytype._meta.get_all_field_names():
+ for fieldname in get_all_field_names(entrytype):
if fieldname in ['id', 'hash_key', 'state']:
continue
try:
@@ -284,6 +313,14 @@ class DjangoORM(StorageBase):
Group.objects.get_or_create(name=metadata['profile'])
else:
profile = None
+
+ flags = {'dry_run': False, 'only_important': False}
+ for flag in stats.findall('./Flags/Flag'):
+ value = flag.get('value', default='false').lower() == 'true'
+ name = flag.get('name')
+ if name in flags:
+ flags[name] = value
+
inter = Interaction(client=client,
timestamp=timestamp,
state=stats.get('state', default="unknown"),
@@ -292,7 +329,8 @@ class DjangoORM(StorageBase):
good_count=stats.get('good', default="0"),
total_count=stats.get('total', default="0"),
server=server,
- profile=profile)
+ profile=profile,
+ **flags)
inter.save()
self.logger.debug("Interaction for %s at %s with INSERTED in to db" %
(client.id, timestamp))
@@ -374,8 +412,12 @@ class DjangoORM(StorageBase):
finally:
self.logger.debug("%s: Closing database connection" %
self.__class__.__name__)
- db.close_connection()
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ for connection in django.db.connections.all():
+ connection.close()
+ else:
+ django.db.close_connection()
def validate(self):
"""Validate backend storage. Should be called once when loaded"""
diff --git a/src/lib/Bcfg2/Reporting/migrations/0001_initial.py b/src/lib/Bcfg2/Reporting/migrations/0001_initial.py
index 609290edb..6a52a8b2d 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0001_initial.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0001_initial.py
@@ -1,465 +1,281 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
- # Adding model 'Client'
- db.create_table('Reporting_client', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('creation', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_interaction', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='parent_client', null=True, to=orm['Reporting.Interaction'])),
- ('expiration', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
- ))
- db.send_create_signal('Reporting', ['Client'])
-
- # Adding model 'Interaction'
- db.create_table('Reporting_interaction', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='interactions', to=orm['Reporting.Client'])),
- ('timestamp', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.CharField')(max_length=32)),
- ('repo_rev_code', self.gf('django.db.models.fields.CharField')(max_length=64)),
- ('server', self.gf('django.db.models.fields.CharField')(max_length=256)),
- ('good_count', self.gf('django.db.models.fields.IntegerField')()),
- ('total_count', self.gf('django.db.models.fields.IntegerField')()),
- ('bad_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ('modified_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ('extra_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ('profile', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.Group'])),
- ))
- db.send_create_signal('Reporting', ['Interaction'])
-
- # Adding unique constraint on 'Interaction', fields ['client', 'timestamp']
- db.create_unique('Reporting_interaction', ['client_id', 'timestamp'])
-
- # Adding M2M table for field actions on 'Interaction'
- db.create_table('Reporting_interaction_actions', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('actionentry', models.ForeignKey(orm['Reporting.actionentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_actions', ['interaction_id', 'actionentry_id'])
-
- # Adding M2M table for field packages on 'Interaction'
- db.create_table('Reporting_interaction_packages', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('packageentry', models.ForeignKey(orm['Reporting.packageentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_packages', ['interaction_id', 'packageentry_id'])
-
- # Adding M2M table for field paths on 'Interaction'
- db.create_table('Reporting_interaction_paths', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('pathentry', models.ForeignKey(orm['Reporting.pathentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_paths', ['interaction_id', 'pathentry_id'])
-
- # Adding M2M table for field services on 'Interaction'
- db.create_table('Reporting_interaction_services', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('serviceentry', models.ForeignKey(orm['Reporting.serviceentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_services', ['interaction_id', 'serviceentry_id'])
-
- # Adding M2M table for field failures on 'Interaction'
- db.create_table('Reporting_interaction_failures', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('failureentry', models.ForeignKey(orm['Reporting.failureentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_failures', ['interaction_id', 'failureentry_id'])
-
- # Adding M2M table for field groups on 'Interaction'
- db.create_table('Reporting_interaction_groups', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('group', models.ForeignKey(orm['Reporting.group'], null=False))
- ))
- db.create_unique('Reporting_interaction_groups', ['interaction_id', 'group_id'])
-
- # Adding M2M table for field bundles on 'Interaction'
- db.create_table('Reporting_interaction_bundles', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('bundle', models.ForeignKey(orm['Reporting.bundle'], null=False))
- ))
- db.create_unique('Reporting_interaction_bundles', ['interaction_id', 'bundle_id'])
-
- # Adding model 'Performance'
- db.create_table('Reporting_performance', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('interaction', self.gf('django.db.models.fields.related.ForeignKey')(related_name='performance_items', to=orm['Reporting.Interaction'])),
- ('metric', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('value', self.gf('django.db.models.fields.DecimalField')(max_digits=32, decimal_places=16)),
- ))
- db.send_create_signal('Reporting', ['Performance'])
-
- # Adding model 'Group'
- db.create_table('Reporting_group', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
- ('profile', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('public', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('category', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
- ('comment', self.gf('django.db.models.fields.TextField')(blank=True)),
- ))
- db.send_create_signal('Reporting', ['Group'])
-
- # Adding M2M table for field groups on 'Group'
- db.create_table('Reporting_group_groups', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('from_group', models.ForeignKey(orm['Reporting.group'], null=False)),
- ('to_group', models.ForeignKey(orm['Reporting.group'], null=False))
- ))
- db.create_unique('Reporting_group_groups', ['from_group_id', 'to_group_id'])
-
- # Adding M2M table for field bundles on 'Group'
- db.create_table('Reporting_group_bundles', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('group', models.ForeignKey(orm['Reporting.group'], null=False)),
- ('bundle', models.ForeignKey(orm['Reporting.bundle'], null=False))
- ))
- db.create_unique('Reporting_group_bundles', ['group_id', 'bundle_id'])
-
- # Adding model 'Bundle'
- db.create_table('Reporting_bundle', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
- ))
- db.send_create_signal('Reporting', ['Bundle'])
-
- # Adding model 'FilePerms'
- db.create_table('Reporting_fileperms', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('owner', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('group', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('perms', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ))
- db.send_create_signal('Reporting', ['FilePerms'])
-
- # Adding unique constraint on 'FilePerms', fields ['owner', 'group', 'perms']
- db.create_unique('Reporting_fileperms', ['owner', 'group', 'perms'])
-
- # Adding model 'FileAcl'
- db.create_table('Reporting_fileacl', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ))
- db.send_create_signal('Reporting', ['FileAcl'])
-
- # Adding model 'FailureEntry'
- db.create_table('Reporting_failureentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('entry_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('message', self.gf('django.db.models.fields.TextField')()),
- ))
- db.send_create_signal('Reporting', ['FailureEntry'])
-
- # Adding model 'ActionEntry'
- db.create_table('Reporting_actionentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('status', self.gf('django.db.models.fields.CharField')(default='check', max_length=128)),
- ('output', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ))
- db.send_create_signal('Reporting', ['ActionEntry'])
-
- # Adding model 'PackageEntry'
- db.create_table('Reporting_packageentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('target_version', self.gf('django.db.models.fields.CharField')(default='', max_length=1024)),
- ('current_version', self.gf('django.db.models.fields.CharField')(max_length=1024)),
- ('verification_details', self.gf('django.db.models.fields.TextField')(default='')),
- ))
- db.send_create_signal('Reporting', ['PackageEntry'])
-
- # Adding model 'PathEntry'
- db.create_table('Reporting_pathentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('path_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('target_perms', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.FilePerms'])),
- ('current_perms', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.FilePerms'])),
- ('detail_type', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ('details', self.gf('django.db.models.fields.TextField')(default='')),
- ))
- db.send_create_signal('Reporting', ['PathEntry'])
-
- # Adding M2M table for field acls on 'PathEntry'
- db.create_table('Reporting_pathentry_acls', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('pathentry', models.ForeignKey(orm['Reporting.pathentry'], null=False)),
- ('fileacl', models.ForeignKey(orm['Reporting.fileacl'], null=False))
- ))
- db.create_unique('Reporting_pathentry_acls', ['pathentry_id', 'fileacl_id'])
-
- # Adding model 'LinkEntry'
- db.create_table('Reporting_linkentry', (
- ('pathentry_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['Reporting.PathEntry'], unique=True, primary_key=True)),
- ('target_path', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
- ('current_path', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
- ))
- db.send_create_signal('Reporting', ['LinkEntry'])
-
- # Adding model 'DeviceEntry'
- db.create_table('Reporting_deviceentry', (
- ('pathentry_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['Reporting.PathEntry'], unique=True, primary_key=True)),
- ('device_type', self.gf('django.db.models.fields.CharField')(max_length=16)),
- ('target_major', self.gf('django.db.models.fields.IntegerField')()),
- ('target_minor', self.gf('django.db.models.fields.IntegerField')()),
- ('current_major', self.gf('django.db.models.fields.IntegerField')()),
- ('current_minor', self.gf('django.db.models.fields.IntegerField')()),
- ))
- db.send_create_signal('Reporting', ['DeviceEntry'])
-
- # Adding model 'ServiceEntry'
- db.create_table('Reporting_serviceentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('target_status', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
- ('current_status', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
- ))
- db.send_create_signal('Reporting', ['ServiceEntry'])
-
-
- def backwards(self, orm):
- # Removing unique constraint on 'FilePerms', fields ['owner', 'group', 'perms']
- db.delete_unique('Reporting_fileperms', ['owner', 'group', 'perms'])
-
- # Removing unique constraint on 'Interaction', fields ['client', 'timestamp']
- db.delete_unique('Reporting_interaction', ['client_id', 'timestamp'])
-
- # Deleting model 'Client'
- db.delete_table('Reporting_client')
-
- # Deleting model 'Interaction'
- db.delete_table('Reporting_interaction')
-
- # Removing M2M table for field actions on 'Interaction'
- db.delete_table('Reporting_interaction_actions')
-
- # Removing M2M table for field packages on 'Interaction'
- db.delete_table('Reporting_interaction_packages')
-
- # Removing M2M table for field paths on 'Interaction'
- db.delete_table('Reporting_interaction_paths')
-
- # Removing M2M table for field services on 'Interaction'
- db.delete_table('Reporting_interaction_services')
-
- # Removing M2M table for field failures on 'Interaction'
- db.delete_table('Reporting_interaction_failures')
-
- # Removing M2M table for field groups on 'Interaction'
- db.delete_table('Reporting_interaction_groups')
-
- # Removing M2M table for field bundles on 'Interaction'
- db.delete_table('Reporting_interaction_bundles')
-
- # Deleting model 'Performance'
- db.delete_table('Reporting_performance')
-
- # Deleting model 'Group'
- db.delete_table('Reporting_group')
-
- # Removing M2M table for field groups on 'Group'
- db.delete_table('Reporting_group_groups')
-
- # Removing M2M table for field bundles on 'Group'
- db.delete_table('Reporting_group_bundles')
-
- # Deleting model 'Bundle'
- db.delete_table('Reporting_bundle')
-
- # Deleting model 'FilePerms'
- db.delete_table('Reporting_fileperms')
-
- # Deleting model 'FileAcl'
- db.delete_table('Reporting_fileacl')
-
- # Deleting model 'FailureEntry'
- db.delete_table('Reporting_failureentry')
-
- # Deleting model 'ActionEntry'
- db.delete_table('Reporting_actionentry')
-
- # Deleting model 'PackageEntry'
- db.delete_table('Reporting_packageentry')
-
- # Deleting model 'PathEntry'
- db.delete_table('Reporting_pathentry')
-
- # Removing M2M table for field acls on 'PathEntry'
- db.delete_table('Reporting_pathentry_acls')
-
- # Deleting model 'LinkEntry'
- db.delete_table('Reporting_linkentry')
-
- # Deleting model 'DeviceEntry'
- db.delete_table('Reporting_deviceentry')
-
- # Deleting model 'ServiceEntry'
- db.delete_table('Reporting_serviceentry')
-
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'perms'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'perms': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- }
- }
-
- complete_apps = ['Reporting'] \ No newline at end of file
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ActionEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.IntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('status', models.CharField(default=b'check', max_length=128)),
+ ('output', models.IntegerField(default=0)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Bundle',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(unique=True, max_length=255)),
+ ],
+ options={
+ 'ordering': ('name',),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Client',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('creation', models.DateTimeField(auto_now_add=True)),
+ ('name', models.CharField(max_length=128)),
+ ('expiration', models.DateTimeField(null=True, blank=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FailureEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.IntegerField(editable=False, db_index=True)),
+ ('entry_type', models.CharField(max_length=128)),
+ ('message', models.TextField()),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FileAcl',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FilePerms',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('owner', models.CharField(max_length=128)),
+ ('group', models.CharField(max_length=128)),
+ ('perms', models.CharField(max_length=128)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Group',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(unique=True, max_length=255)),
+ ('profile', models.BooleanField(default=False)),
+ ('public', models.BooleanField(default=False)),
+ ('category', models.CharField(max_length=1024, blank=True)),
+ ('comment', models.TextField(blank=True)),
+ ('bundles', models.ManyToManyField(to='Reporting.Bundle')),
+ ('groups', models.ManyToManyField(to='Reporting.Group')),
+ ],
+ options={
+ 'ordering': ('name',),
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Interaction',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('timestamp', models.DateTimeField(db_index=True)),
+ ('state', models.CharField(max_length=32)),
+ ('repo_rev_code', models.CharField(max_length=64)),
+ ('server', models.CharField(max_length=256)),
+ ('good_count', models.IntegerField()),
+ ('total_count', models.IntegerField()),
+ ('bad_count', models.IntegerField(default=0)),
+ ('modified_count', models.IntegerField(default=0)),
+ ('extra_count', models.IntegerField(default=0)),
+ ('actions', models.ManyToManyField(to='Reporting.ActionEntry')),
+ ('bundles', models.ManyToManyField(to='Reporting.Bundle')),
+ ('client', models.ForeignKey(related_name='interactions', to='Reporting.Client')),
+ ('failures', models.ManyToManyField(to='Reporting.FailureEntry')),
+ ('groups', models.ManyToManyField(to='Reporting.Group')),
+ ],
+ options={
+ 'ordering': ['-timestamp'],
+ 'get_latest_by': 'timestamp',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='PackageEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.IntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('target_version', models.CharField(default=b'', max_length=1024)),
+ ('current_version', models.CharField(max_length=1024)),
+ ('verification_details', models.TextField(default=b'')),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='PathEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.IntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('path_type', models.CharField(max_length=128, choices=[(b'device', b'Device'), (b'directory', b'Directory'), (b'hardlink', b'Hard Link'), (b'nonexistent', b'Non Existent'), (b'permissions', b'Permissions'), (b'symlink', b'Symlink')])),
+ ('detail_type', models.IntegerField(default=0, choices=[(0, b'Unused'), (1, b'Diff'), (2, b'Binary'), (3, b'Sensitive'), (4, b'Size limit exceeded'), (5, b'VCS output'), (6, b'Pruned paths')])),
+ ('details', models.TextField(default=b'')),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='LinkEntry',
+ fields=[
+ ('pathentry_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='Reporting.PathEntry')),
+ ('target_path', models.CharField(max_length=1024, blank=True)),
+ ('current_path', models.CharField(max_length=1024, blank=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=('Reporting.pathentry',),
+ ),
+ migrations.CreateModel(
+ name='DeviceEntry',
+ fields=[
+ ('pathentry_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='Reporting.PathEntry')),
+ ('device_type', models.CharField(max_length=16, choices=[(b'block', b'Block'), (b'char', b'Char'), (b'fifo', b'Fifo')])),
+ ('target_major', models.IntegerField()),
+ ('target_minor', models.IntegerField()),
+ ('current_major', models.IntegerField()),
+ ('current_minor', models.IntegerField()),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=('Reporting.pathentry',),
+ ),
+ migrations.CreateModel(
+ name='Performance',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('metric', models.CharField(max_length=128)),
+ ('value', models.DecimalField(max_digits=32, decimal_places=16)),
+ ('interaction', models.ForeignKey(related_name='performance_items', to='Reporting.Interaction')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ServiceEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.IntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('target_status', models.CharField(default=b'', max_length=128)),
+ ('current_status', models.CharField(default=b'', max_length=128)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='pathentry',
+ name='acls',
+ field=models.ManyToManyField(to='Reporting.FileAcl'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='pathentry',
+ name='current_perms',
+ field=models.ForeignKey(related_name='+', to='Reporting.FilePerms'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='pathentry',
+ name='target_perms',
+ field=models.ForeignKey(related_name='+', to='Reporting.FilePerms'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='packages',
+ field=models.ManyToManyField(to='Reporting.PackageEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='paths',
+ field=models.ManyToManyField(to='Reporting.PathEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='profile',
+ field=models.ForeignKey(related_name='+', to='Reporting.Group'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='services',
+ field=models.ManyToManyField(to='Reporting.ServiceEntry'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='interaction',
+ unique_together=set([('client', 'timestamp')]),
+ ),
+ migrations.AlterUniqueTogether(
+ name='fileperms',
+ unique_together=set([('owner', 'group', 'perms')]),
+ ),
+ migrations.AddField(
+ model_name='client',
+ name='current_interaction',
+ field=models.ForeignKey(related_name='parent_client', blank=True, to='Reporting.Interaction', null=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0002_convert_perms_to_mode.py b/src/lib/Bcfg2/Reporting/migrations/0002_convert_perms_to_mode.py
index 37cdd146c..48bfd6412 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0002_convert_perms_to_mode.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0002_convert_perms_to_mode.py
@@ -1,170 +1,23 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-from django.conf import settings
+from __future__ import unicode_literals
-class Migration(SchemaMigration):
+from django.db import models, migrations
- def forwards(self, orm):
- # Removing unique constraint on 'FilePerms', fields ['owner', 'perms', 'group']
- db.delete_unique('Reporting_fileperms', ['owner', 'perms', 'group'])
- # Renaming field 'FilePerms.perms' to 'FilePerms.mode'
- db.rename_column('Reporting_fileperms', 'perms', 'mode')
+class Migration(migrations.Migration):
- if not settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
- # Adding unique constraint on 'FilePerms', fields ['owner', 'group', 'mode']
- db.create_unique('Reporting_fileperms', ['owner', 'group', 'mode'])
+ dependencies = [
+ ('Reporting', '0001_initial'),
+ ]
-
- def backwards(self, orm):
- # Removing unique constraint on 'FilePerms', fields ['owner', 'group', 'mode']
- db.delete_unique('Reporting_fileperms', ['owner', 'group', 'mode'])
-
- # Renaming field 'FilePerms.mode' to 'FilePerms.perms'
- db.rename_column('Reporting_fileperms', 'mode', 'perms')
-
- if not settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
- # Adding unique constraint on 'FilePerms', fields ['owner', 'perms', 'group']
- db.create_unique('Reporting_fileperms', ['owner', 'perms', 'group'])
-
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- }
- }
-
- complete_apps = ['Reporting']
+ operations = [
+ migrations.RenameField(
+ model_name='fileperms',
+ old_name='perms',
+ new_name='mode',
+ ),
+ migrations.AlterUniqueTogether(
+ name='fileperms',
+ unique_together=set([('owner', 'group', 'mode')]),
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0003_expand_hash_key.py b/src/lib/Bcfg2/Reporting/migrations/0003_expand_hash_key.py
index 2da1fa722..b9ae207dd 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0003_expand_hash_key.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0003_expand_hash_key.py
@@ -1,180 +1,44 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Changing field 'FailureEntry.hash_key'
- db.alter_column('Reporting_failureentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
-
- # Changing field 'PackageEntry.hash_key'
- db.alter_column('Reporting_packageentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
-
- # Changing field 'ServiceEntry.hash_key'
- db.alter_column('Reporting_serviceentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
-
- # Changing field 'PathEntry.hash_key'
- db.alter_column('Reporting_pathentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
-
- # Changing field 'ActionEntry.hash_key'
- db.alter_column('Reporting_actionentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
-
- def backwards(self, orm):
-
- # Changing field 'FailureEntry.hash_key'
- db.alter_column('Reporting_failureentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
-
- # Changing field 'PackageEntry.hash_key'
- db.alter_column('Reporting_packageentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
-
- # Changing field 'ServiceEntry.hash_key'
- db.alter_column('Reporting_serviceentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
-
- # Changing field 'PathEntry.hash_key'
- db.alter_column('Reporting_pathentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
-
- # Changing field 'ActionEntry.hash_key'
- db.alter_column('Reporting_actionentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- }
- }
-
- complete_apps = ['Reporting'] \ No newline at end of file
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('Reporting', '0002_convert_perms_to_mode'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='actionentry',
+ name='hash_key',
+ field=models.BigIntegerField(editable=False, db_index=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='failureentry',
+ name='hash_key',
+ field=models.BigIntegerField(editable=False, db_index=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='packageentry',
+ name='hash_key',
+ field=models.BigIntegerField(editable=False, db_index=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='pathentry',
+ name='hash_key',
+ field=models.BigIntegerField(editable=False, db_index=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='serviceentry',
+ name='hash_key',
+ field=models.BigIntegerField(editable=False, db_index=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0004_profile_can_be_null.py b/src/lib/Bcfg2/Reporting/migrations/0004_profile_can_be_null.py
index 26a053b67..d0b2de417 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0004_profile_can_be_null.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0004_profile_can_be_null.py
@@ -1,156 +1,20 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
+from __future__ import unicode_literals
+from django.db import models, migrations
-class Migration(SchemaMigration):
- def forwards(self, orm):
+class Migration(migrations.Migration):
- # Changing field 'Interaction.profile'
- db.alter_column('Reporting_interaction', 'profile_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['Reporting.Group']))
+ dependencies = [
+ ('Reporting', '0003_expand_hash_key'),
+ ]
- def backwards(self, orm):
-
- # User chose to not deal with backwards NULL issues for 'Interaction.profile'
- raise RuntimeError("Cannot reverse this migration. 'Interaction.profile' and its values cannot be restored.")
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- }
- }
-
- complete_apps = ['Reporting'] \ No newline at end of file
+ operations = [
+ migrations.AlterField(
+ model_name='interaction',
+ name='profile',
+ field=models.ForeignKey(related_name='+', to='Reporting.Group', null=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py b/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py
index d5f5d801a..0c6934b10 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0005_add_selinux_entry_support.py
@@ -1,485 +1,222 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
- # Adding model 'SELoginEntry'
- db.create_table('Reporting_seloginentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ))
- db.send_create_signal('Reporting', ['SELoginEntry'])
-
- # Adding model 'SEUserEntry'
- db.create_table('Reporting_seuserentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('roles', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_roles', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ('prefix', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_prefix', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ))
- db.send_create_signal('Reporting', ['SEUserEntry'])
-
- # Adding model 'SEBooleanEntry'
- db.create_table('Reporting_sebooleanentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('value', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ))
- db.send_create_signal('Reporting', ['SEBooleanEntry'])
-
- # Adding model 'SENodeEntry'
- db.create_table('Reporting_senodeentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ('proto', self.gf('django.db.models.fields.CharField')(max_length=4)),
- ))
- db.send_create_signal('Reporting', ['SENodeEntry'])
-
- # Adding model 'SEFcontextEntry'
- db.create_table('Reporting_sefcontextentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ('filetype', self.gf('django.db.models.fields.CharField')(max_length=16)),
- ))
- db.send_create_signal('Reporting', ['SEFcontextEntry'])
-
- # Adding model 'SEInterfaceEntry'
- db.create_table('Reporting_seinterfaceentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ))
- db.send_create_signal('Reporting', ['SEInterfaceEntry'])
-
- # Adding model 'SEPermissiveEntry'
- db.create_table('Reporting_sepermissiveentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ))
- db.send_create_signal('Reporting', ['SEPermissiveEntry'])
-
- # Adding model 'SEModuleEntry'
- db.create_table('Reporting_semoduleentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('current_disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ))
- db.send_create_signal('Reporting', ['SEModuleEntry'])
-
- # Adding model 'SEPortEntry'
- db.create_table('Reporting_seportentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
- ))
- db.send_create_signal('Reporting', ['SEPortEntry'])
-
- # Adding M2M table for field sebooleans on 'Interaction'
- db.create_table('Reporting_interaction_sebooleans', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('sebooleanentry', models.ForeignKey(orm['Reporting.sebooleanentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_sebooleans', ['interaction_id', 'sebooleanentry_id'])
-
- # Adding M2M table for field seports on 'Interaction'
- db.create_table('Reporting_interaction_seports', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('seportentry', models.ForeignKey(orm['Reporting.seportentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_seports', ['interaction_id', 'seportentry_id'])
-
- # Adding M2M table for field sefcontexts on 'Interaction'
- db.create_table('Reporting_interaction_sefcontexts', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('sefcontextentry', models.ForeignKey(orm['Reporting.sefcontextentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_sefcontexts', ['interaction_id', 'sefcontextentry_id'])
-
- # Adding M2M table for field senodes on 'Interaction'
- db.create_table('Reporting_interaction_senodes', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('senodeentry', models.ForeignKey(orm['Reporting.senodeentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_senodes', ['interaction_id', 'senodeentry_id'])
-
- # Adding M2M table for field selogins on 'Interaction'
- db.create_table('Reporting_interaction_selogins', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('seloginentry', models.ForeignKey(orm['Reporting.seloginentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_selogins', ['interaction_id', 'seloginentry_id'])
-
- # Adding M2M table for field seusers on 'Interaction'
- db.create_table('Reporting_interaction_seusers', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('seuserentry', models.ForeignKey(orm['Reporting.seuserentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_seusers', ['interaction_id', 'seuserentry_id'])
-
- # Adding M2M table for field seinterfaces on 'Interaction'
- db.create_table('Reporting_interaction_seinterfaces', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('seinterfaceentry', models.ForeignKey(orm['Reporting.seinterfaceentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_seinterfaces', ['interaction_id', 'seinterfaceentry_id'])
-
- # Adding M2M table for field sepermissives on 'Interaction'
- db.create_table('Reporting_interaction_sepermissives', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('sepermissiveentry', models.ForeignKey(orm['Reporting.sepermissiveentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_sepermissives', ['interaction_id', 'sepermissiveentry_id'])
-
- # Adding M2M table for field semodules on 'Interaction'
- db.create_table('Reporting_interaction_semodules', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('semoduleentry', models.ForeignKey(orm['Reporting.semoduleentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_semodules', ['interaction_id', 'semoduleentry_id'])
-
-
- def backwards(self, orm):
- # Deleting model 'SELoginEntry'
- db.delete_table('Reporting_seloginentry')
-
- # Deleting model 'SEUserEntry'
- db.delete_table('Reporting_seuserentry')
-
- # Deleting model 'SEBooleanEntry'
- db.delete_table('Reporting_sebooleanentry')
-
- # Deleting model 'SENodeEntry'
- db.delete_table('Reporting_senodeentry')
-
- # Deleting model 'SEFcontextEntry'
- db.delete_table('Reporting_sefcontextentry')
-
- # Deleting model 'SEInterfaceEntry'
- db.delete_table('Reporting_seinterfaceentry')
-
- # Deleting model 'SEPermissiveEntry'
- db.delete_table('Reporting_sepermissiveentry')
-
- # Deleting model 'SEModuleEntry'
- db.delete_table('Reporting_semoduleentry')
-
- # Deleting model 'SEPortEntry'
- db.delete_table('Reporting_seportentry')
-
- # Removing M2M table for field sebooleans on 'Interaction'
- db.delete_table('Reporting_interaction_sebooleans')
-
- # Removing M2M table for field seports on 'Interaction'
- db.delete_table('Reporting_interaction_seports')
-
- # Removing M2M table for field sefcontexts on 'Interaction'
- db.delete_table('Reporting_interaction_sefcontexts')
-
- # Removing M2M table for field senodes on 'Interaction'
- db.delete_table('Reporting_interaction_senodes')
-
- # Removing M2M table for field selogins on 'Interaction'
- db.delete_table('Reporting_interaction_selogins')
-
- # Removing M2M table for field seusers on 'Interaction'
- db.delete_table('Reporting_interaction_seusers')
-
- # Removing M2M table for field seinterfaces on 'Interaction'
- db.delete_table('Reporting_interaction_seinterfaces')
-
- # Removing M2M table for field sepermissives on 'Interaction'
- db.delete_table('Reporting_interaction_sepermissives')
-
- # Removing M2M table for field semodules on 'Interaction'
- db.delete_table('Reporting_interaction_semodules')
-
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
- 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
- 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
- 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
- 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
- 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
- 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
- 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.sebooleanentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
- },
- 'Reporting.sefcontextentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seinterfaceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seloginentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
- 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.semoduleentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
- 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.senodeentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.sepermissiveentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seportentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- },
- 'Reporting.seuserentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
- 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- }
- }
-
- complete_apps = ['Reporting'] \ No newline at end of file
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('Reporting', '0004_profile_can_be_null'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SEBooleanEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('value', models.BooleanField(default=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEFcontextEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('selinuxtype', models.CharField(max_length=128)),
+ ('current_selinuxtype', models.CharField(max_length=128, null=True)),
+ ('filetype', models.CharField(max_length=16)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEInterfaceEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('selinuxtype', models.CharField(max_length=128)),
+ ('current_selinuxtype', models.CharField(max_length=128, null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SELoginEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('selinuxuser', models.CharField(max_length=128)),
+ ('current_selinuxuser', models.CharField(max_length=128, null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEModuleEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('disabled', models.BooleanField(default=False)),
+ ('current_disabled', models.BooleanField(default=False)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SENodeEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('selinuxtype', models.CharField(max_length=128)),
+ ('current_selinuxtype', models.CharField(max_length=128, null=True)),
+ ('proto', models.CharField(max_length=4)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEPermissiveEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEPortEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('selinuxtype', models.CharField(max_length=128)),
+ ('current_selinuxtype', models.CharField(max_length=128, null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='SEUserEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('roles', models.CharField(max_length=128)),
+ ('current_roles', models.CharField(max_length=128, null=True)),
+ ('prefix', models.CharField(max_length=128)),
+ ('current_prefix', models.CharField(max_length=128, null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='sebooleans',
+ field=models.ManyToManyField(to='Reporting.SEBooleanEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='sefcontexts',
+ field=models.ManyToManyField(to='Reporting.SEFcontextEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='seinterfaces',
+ field=models.ManyToManyField(to='Reporting.SEInterfaceEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='selogins',
+ field=models.ManyToManyField(to='Reporting.SELoginEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='semodules',
+ field=models.ManyToManyField(to='Reporting.SEModuleEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='senodes',
+ field=models.ManyToManyField(to='Reporting.SENodeEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='sepermissives',
+ field=models.ManyToManyField(to='Reporting.SEPermissiveEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='seports',
+ field=models.ManyToManyField(to='Reporting.SEPortEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='seusers',
+ field=models.ManyToManyField(to='Reporting.SEUserEntry'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py b/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py
index d86e663d5..4853faaf3 100644
--- a/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py
+++ b/src/lib/Bcfg2/Reporting/migrations/0006_add_user_group_entry_support.py
@@ -1,340 +1,68 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
+from __future__ import unicode_literals
+from django.db import models, migrations
-class Migration(SchemaMigration):
- def forwards(self, orm):
- # Adding model 'POSIXGroupEntry'
- db.create_table('Reporting_posixgroupentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
- ('current_gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
- ))
- db.send_create_signal('Reporting', ['POSIXGroupEntry'])
+class Migration(migrations.Migration):
- # Adding model 'POSIXUserEntry'
- db.create_table('Reporting_posixuserentry', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
- ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('state', self.gf('django.db.models.fields.IntegerField')()),
- ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
- ('current_uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
- ('group', self.gf('django.db.models.fields.CharField')(max_length=64)),
- ('current_group', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)),
- ('gecos', self.gf('django.db.models.fields.CharField')(max_length=1024)),
- ('current_gecos', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
- ('home', self.gf('django.db.models.fields.CharField')(max_length=1024)),
- ('current_home', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
- ('shell', self.gf('django.db.models.fields.CharField')(default='/bin/bash', max_length=1024)),
- ('current_shell', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
- ))
- db.send_create_signal('Reporting', ['POSIXUserEntry'])
+ dependencies = [
+ ('Reporting', '0005_add_selinux_entry_support'),
+ ]
- # Adding M2M table for field posixusers on 'Interaction'
- db.create_table('Reporting_interaction_posixusers', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('posixuserentry', models.ForeignKey(orm['Reporting.posixuserentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_posixusers', ['interaction_id', 'posixuserentry_id'])
-
- # Adding M2M table for field posixgroups on 'Interaction'
- db.create_table('Reporting_interaction_posixgroups', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
- ('posixgroupentry', models.ForeignKey(orm['Reporting.posixgroupentry'], null=False))
- ))
- db.create_unique('Reporting_interaction_posixgroups', ['interaction_id', 'posixgroupentry_id'])
-
-
- def backwards(self, orm):
- # Deleting model 'POSIXGroupEntry'
- db.delete_table('Reporting_posixgroupentry')
-
- # Deleting model 'POSIXUserEntry'
- db.delete_table('Reporting_posixuserentry')
-
- # Removing M2M table for field posixusers on 'Interaction'
- db.delete_table('Reporting_interaction_posixusers')
-
- # Removing M2M table for field posixgroups on 'Interaction'
- db.delete_table('Reporting_interaction_posixgroups')
-
-
- models = {
- 'Reporting.actionentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
- },
- 'Reporting.bundle': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
- },
- 'Reporting.client': {
- 'Meta': {'object_name': 'Client'},
- 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
- 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.deviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_major': ('django.db.models.fields.IntegerField', [], {}),
- 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
- 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_major': ('django.db.models.fields.IntegerField', [], {}),
- 'target_minor': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.failureentry': {
- 'Meta': {'object_name': 'FailureEntry'},
- 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.TextField', [], {}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileacl': {
- 'Meta': {'object_name': 'FileAcl'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
- },
- 'Reporting.fileperms': {
- 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
- },
- 'Reporting.group': {
- 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
- },
- 'Reporting.interaction': {
- 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
- 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
- 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
- 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
- 'good_count': ('django.db.models.fields.IntegerField', [], {}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
- 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
- 'posixgroups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXGroupEntry']", 'symmetrical': 'False'}),
- 'posixusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXUserEntry']", 'symmetrical': 'False'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
- 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
- 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
- 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
- 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
- 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
- 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
- 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
- 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
- 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
- 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
- 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
- 'total_count': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.linkentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
- 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
- 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
- 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
- },
- 'Reporting.packageentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
- 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
- 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
- },
- 'Reporting.pathentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
- 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
- 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
- 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
- },
- 'Reporting.performance': {
- 'Meta': {'object_name': 'Performance'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
- 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
- },
- 'Reporting.posixgroupentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXGroupEntry'},
- 'current_gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.posixuserentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXUserEntry'},
- 'current_gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
- 'current_group': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
- 'current_home': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
- 'current_shell': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
- 'current_uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'group': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'home': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'shell': ('django.db.models.fields.CharField', [], {'default': "'/bin/bash'", 'max_length': '1024'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
- },
- 'Reporting.sebooleanentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
- },
- 'Reporting.sefcontextentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seinterfaceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seloginentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
- 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.semoduleentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
- 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.senodeentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.sepermissiveentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.seportentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
- 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- },
- 'Reporting.serviceentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
- 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'state': ('django.db.models.fields.IntegerField', [], {}),
- 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
- },
- 'Reporting.seuserentry': {
- 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
- 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
- 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
- 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'state': ('django.db.models.fields.IntegerField', [], {})
- }
- }
-
- complete_apps = ['Reporting'] \ No newline at end of file
+ operations = [
+ migrations.CreateModel(
+ name='POSIXGroupEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('gid', models.IntegerField(null=True)),
+ ('current_gid', models.IntegerField(null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='POSIXUserEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=128, db_index=True)),
+ ('hash_key', models.BigIntegerField(editable=False, db_index=True)),
+ ('state', models.IntegerField(choices=[(0, b'Good'), (1, b'Bad'), (2, b'Modified'), (3, b'Extra')])),
+ ('exists', models.BooleanField(default=True)),
+ ('uid', models.IntegerField(null=True)),
+ ('current_uid', models.IntegerField(null=True)),
+ ('group', models.CharField(max_length=64)),
+ ('current_group', models.CharField(max_length=64, null=True)),
+ ('gecos', models.CharField(max_length=1024)),
+ ('current_gecos', models.CharField(max_length=1024, null=True)),
+ ('home', models.CharField(max_length=1024)),
+ ('current_home', models.CharField(max_length=1024, null=True)),
+ ('shell', models.CharField(default=b'/bin/bash', max_length=1024)),
+ ('current_shell', models.CharField(max_length=1024, null=True)),
+ ],
+ options={
+ 'ordering': ('state', 'name'),
+ 'abstract': False,
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='posixgroups',
+ field=models.ManyToManyField(to='Reporting.POSIXGroupEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='posixusers',
+ field=models.ManyToManyField(to='Reporting.POSIXUserEntry'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/migrations/0007_add_flag_fields_interaction.py b/src/lib/Bcfg2/Reporting/migrations/0007_add_flag_fields_interaction.py
new file mode 100644
index 000000000..1d1274288
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/migrations/0007_add_flag_fields_interaction.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('Reporting', '0006_add_user_group_entry_support'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interaction',
+ name='dry_run',
+ field=models.BooleanField(default=False),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='interaction',
+ name='only_important',
+ field=models.BooleanField(default=False),
+ preserve_default=True,
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index ae6f6731b..6ba7d3765 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -1,9 +1,10 @@
"""Django models for Bcfg2 reports."""
import sys
+import django
from django.core.exceptions import ImproperlyConfigured
try:
- from django.db import models, backend, connections
+ from django.db import models, connections
except ImproperlyConfigured:
e = sys.exc_info()[1]
print("Reports: unable to import django models: %s" % e)
@@ -61,11 +62,15 @@ def _quote(value):
"""
global _our_backend
if not _our_backend:
- try:
- _our_backend = backend.DatabaseOperations(
- connections[get_db_label('Reporting')])
- except TypeError:
- _our_backend = backend.DatabaseOperations()
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ _our_backend = connections[get_db_label('Reporting')].ops
+ else:
+ from django.db import backend
+ try:
+ _our_backend = backend.DatabaseOperations(
+ connections[get_db_label('Reporting')])
+ except TypeError:
+ _our_backend = backend.DatabaseOperations()
return _our_backend.quote_name(value)
@@ -144,6 +149,8 @@ class Interaction(models.Model):
bad_count = models.IntegerField(default=0)
modified_count = models.IntegerField(default=0)
extra_count = models.IntegerField(default=0)
+ dry_run = models.BooleanField(default=False)
+ only_important = models.BooleanField(default=False)
actions = models.ManyToManyField("ActionEntry")
packages = models.ManyToManyField("PackageEntry")
@@ -630,7 +637,7 @@ class POSIXGroupEntry(SuccessEntry):
class PackageEntry(SuccessEntry):
""" The new model for package information """
- # if this is an extra entry trget_version will be empty
+ # if this is an extra entry target_version will be empty
target_version = models.CharField(max_length=1024, default='')
current_version = models.CharField(max_length=1024)
verification_details = models.TextField(default="")
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0001_initial.py b/src/lib/Bcfg2/Reporting/south_migrations/0001_initial.py
new file mode 100644
index 000000000..609290edb
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0001_initial.py
@@ -0,0 +1,465 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Client'
+ db.create_table('Reporting_client', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('creation', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_interaction', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='parent_client', null=True, to=orm['Reporting.Interaction'])),
+ ('expiration', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ))
+ db.send_create_signal('Reporting', ['Client'])
+
+ # Adding model 'Interaction'
+ db.create_table('Reporting_interaction', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('client', self.gf('django.db.models.fields.related.ForeignKey')(related_name='interactions', to=orm['Reporting.Client'])),
+ ('timestamp', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.CharField')(max_length=32)),
+ ('repo_rev_code', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('server', self.gf('django.db.models.fields.CharField')(max_length=256)),
+ ('good_count', self.gf('django.db.models.fields.IntegerField')()),
+ ('total_count', self.gf('django.db.models.fields.IntegerField')()),
+ ('bad_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('modified_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('extra_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('profile', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.Group'])),
+ ))
+ db.send_create_signal('Reporting', ['Interaction'])
+
+ # Adding unique constraint on 'Interaction', fields ['client', 'timestamp']
+ db.create_unique('Reporting_interaction', ['client_id', 'timestamp'])
+
+ # Adding M2M table for field actions on 'Interaction'
+ db.create_table('Reporting_interaction_actions', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('actionentry', models.ForeignKey(orm['Reporting.actionentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_actions', ['interaction_id', 'actionentry_id'])
+
+ # Adding M2M table for field packages on 'Interaction'
+ db.create_table('Reporting_interaction_packages', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('packageentry', models.ForeignKey(orm['Reporting.packageentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_packages', ['interaction_id', 'packageentry_id'])
+
+ # Adding M2M table for field paths on 'Interaction'
+ db.create_table('Reporting_interaction_paths', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('pathentry', models.ForeignKey(orm['Reporting.pathentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_paths', ['interaction_id', 'pathentry_id'])
+
+ # Adding M2M table for field services on 'Interaction'
+ db.create_table('Reporting_interaction_services', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('serviceentry', models.ForeignKey(orm['Reporting.serviceentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_services', ['interaction_id', 'serviceentry_id'])
+
+ # Adding M2M table for field failures on 'Interaction'
+ db.create_table('Reporting_interaction_failures', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('failureentry', models.ForeignKey(orm['Reporting.failureentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_failures', ['interaction_id', 'failureentry_id'])
+
+ # Adding M2M table for field groups on 'Interaction'
+ db.create_table('Reporting_interaction_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('group', models.ForeignKey(orm['Reporting.group'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_groups', ['interaction_id', 'group_id'])
+
+ # Adding M2M table for field bundles on 'Interaction'
+ db.create_table('Reporting_interaction_bundles', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('bundle', models.ForeignKey(orm['Reporting.bundle'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_bundles', ['interaction_id', 'bundle_id'])
+
+ # Adding model 'Performance'
+ db.create_table('Reporting_performance', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('interaction', self.gf('django.db.models.fields.related.ForeignKey')(related_name='performance_items', to=orm['Reporting.Interaction'])),
+ ('metric', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('value', self.gf('django.db.models.fields.DecimalField')(max_digits=32, decimal_places=16)),
+ ))
+ db.send_create_signal('Reporting', ['Performance'])
+
+ # Adding model 'Group'
+ db.create_table('Reporting_group', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
+ ('profile', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('public', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('category', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
+ ('comment', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ))
+ db.send_create_signal('Reporting', ['Group'])
+
+ # Adding M2M table for field groups on 'Group'
+ db.create_table('Reporting_group_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('from_group', models.ForeignKey(orm['Reporting.group'], null=False)),
+ ('to_group', models.ForeignKey(orm['Reporting.group'], null=False))
+ ))
+ db.create_unique('Reporting_group_groups', ['from_group_id', 'to_group_id'])
+
+ # Adding M2M table for field bundles on 'Group'
+ db.create_table('Reporting_group_bundles', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('group', models.ForeignKey(orm['Reporting.group'], null=False)),
+ ('bundle', models.ForeignKey(orm['Reporting.bundle'], null=False))
+ ))
+ db.create_unique('Reporting_group_bundles', ['group_id', 'bundle_id'])
+
+ # Adding model 'Bundle'
+ db.create_table('Reporting_bundle', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
+ ))
+ db.send_create_signal('Reporting', ['Bundle'])
+
+ # Adding model 'FilePerms'
+ db.create_table('Reporting_fileperms', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('owner', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('group', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('perms', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ))
+ db.send_create_signal('Reporting', ['FilePerms'])
+
+ # Adding unique constraint on 'FilePerms', fields ['owner', 'group', 'perms']
+ db.create_unique('Reporting_fileperms', ['owner', 'group', 'perms'])
+
+ # Adding model 'FileAcl'
+ db.create_table('Reporting_fileacl', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ))
+ db.send_create_signal('Reporting', ['FileAcl'])
+
+ # Adding model 'FailureEntry'
+ db.create_table('Reporting_failureentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+ ('entry_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('message', self.gf('django.db.models.fields.TextField')()),
+ ))
+ db.send_create_signal('Reporting', ['FailureEntry'])
+
+ # Adding model 'ActionEntry'
+ db.create_table('Reporting_actionentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('status', self.gf('django.db.models.fields.CharField')(default='check', max_length=128)),
+ ('output', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ))
+ db.send_create_signal('Reporting', ['ActionEntry'])
+
+ # Adding model 'PackageEntry'
+ db.create_table('Reporting_packageentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('target_version', self.gf('django.db.models.fields.CharField')(default='', max_length=1024)),
+ ('current_version', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('verification_details', self.gf('django.db.models.fields.TextField')(default='')),
+ ))
+ db.send_create_signal('Reporting', ['PackageEntry'])
+
+ # Adding model 'PathEntry'
+ db.create_table('Reporting_pathentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('path_type', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('target_perms', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.FilePerms'])),
+ ('current_perms', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['Reporting.FilePerms'])),
+ ('detail_type', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('details', self.gf('django.db.models.fields.TextField')(default='')),
+ ))
+ db.send_create_signal('Reporting', ['PathEntry'])
+
+ # Adding M2M table for field acls on 'PathEntry'
+ db.create_table('Reporting_pathentry_acls', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('pathentry', models.ForeignKey(orm['Reporting.pathentry'], null=False)),
+ ('fileacl', models.ForeignKey(orm['Reporting.fileacl'], null=False))
+ ))
+ db.create_unique('Reporting_pathentry_acls', ['pathentry_id', 'fileacl_id'])
+
+ # Adding model 'LinkEntry'
+ db.create_table('Reporting_linkentry', (
+ ('pathentry_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['Reporting.PathEntry'], unique=True, primary_key=True)),
+ ('target_path', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
+ ('current_path', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)),
+ ))
+ db.send_create_signal('Reporting', ['LinkEntry'])
+
+ # Adding model 'DeviceEntry'
+ db.create_table('Reporting_deviceentry', (
+ ('pathentry_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['Reporting.PathEntry'], unique=True, primary_key=True)),
+ ('device_type', self.gf('django.db.models.fields.CharField')(max_length=16)),
+ ('target_major', self.gf('django.db.models.fields.IntegerField')()),
+ ('target_minor', self.gf('django.db.models.fields.IntegerField')()),
+ ('current_major', self.gf('django.db.models.fields.IntegerField')()),
+ ('current_minor', self.gf('django.db.models.fields.IntegerField')()),
+ ))
+ db.send_create_signal('Reporting', ['DeviceEntry'])
+
+ # Adding model 'ServiceEntry'
+ db.create_table('Reporting_serviceentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('target_status', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
+ ('current_status', self.gf('django.db.models.fields.CharField')(default='', max_length=128)),
+ ))
+ db.send_create_signal('Reporting', ['ServiceEntry'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'FilePerms', fields ['owner', 'group', 'perms']
+ db.delete_unique('Reporting_fileperms', ['owner', 'group', 'perms'])
+
+ # Removing unique constraint on 'Interaction', fields ['client', 'timestamp']
+ db.delete_unique('Reporting_interaction', ['client_id', 'timestamp'])
+
+ # Deleting model 'Client'
+ db.delete_table('Reporting_client')
+
+ # Deleting model 'Interaction'
+ db.delete_table('Reporting_interaction')
+
+ # Removing M2M table for field actions on 'Interaction'
+ db.delete_table('Reporting_interaction_actions')
+
+ # Removing M2M table for field packages on 'Interaction'
+ db.delete_table('Reporting_interaction_packages')
+
+ # Removing M2M table for field paths on 'Interaction'
+ db.delete_table('Reporting_interaction_paths')
+
+ # Removing M2M table for field services on 'Interaction'
+ db.delete_table('Reporting_interaction_services')
+
+ # Removing M2M table for field failures on 'Interaction'
+ db.delete_table('Reporting_interaction_failures')
+
+ # Removing M2M table for field groups on 'Interaction'
+ db.delete_table('Reporting_interaction_groups')
+
+ # Removing M2M table for field bundles on 'Interaction'
+ db.delete_table('Reporting_interaction_bundles')
+
+ # Deleting model 'Performance'
+ db.delete_table('Reporting_performance')
+
+ # Deleting model 'Group'
+ db.delete_table('Reporting_group')
+
+ # Removing M2M table for field groups on 'Group'
+ db.delete_table('Reporting_group_groups')
+
+ # Removing M2M table for field bundles on 'Group'
+ db.delete_table('Reporting_group_bundles')
+
+ # Deleting model 'Bundle'
+ db.delete_table('Reporting_bundle')
+
+ # Deleting model 'FilePerms'
+ db.delete_table('Reporting_fileperms')
+
+ # Deleting model 'FileAcl'
+ db.delete_table('Reporting_fileacl')
+
+ # Deleting model 'FailureEntry'
+ db.delete_table('Reporting_failureentry')
+
+ # Deleting model 'ActionEntry'
+ db.delete_table('Reporting_actionentry')
+
+ # Deleting model 'PackageEntry'
+ db.delete_table('Reporting_packageentry')
+
+ # Deleting model 'PathEntry'
+ db.delete_table('Reporting_pathentry')
+
+ # Removing M2M table for field acls on 'PathEntry'
+ db.delete_table('Reporting_pathentry_acls')
+
+ # Deleting model 'LinkEntry'
+ db.delete_table('Reporting_linkentry')
+
+ # Deleting model 'DeviceEntry'
+ db.delete_table('Reporting_deviceentry')
+
+ # Deleting model 'ServiceEntry'
+ db.delete_table('Reporting_serviceentry')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'perms'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'perms': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0002_convert_perms_to_mode.py b/src/lib/Bcfg2/Reporting/south_migrations/0002_convert_perms_to_mode.py
new file mode 100644
index 000000000..37cdd146c
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0002_convert_perms_to_mode.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+from django.conf import settings
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Removing unique constraint on 'FilePerms', fields ['owner', 'perms', 'group']
+ db.delete_unique('Reporting_fileperms', ['owner', 'perms', 'group'])
+
+ # Renaming field 'FilePerms.perms' to 'FilePerms.mode'
+ db.rename_column('Reporting_fileperms', 'perms', 'mode')
+
+ if not settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
+ # Adding unique constraint on 'FilePerms', fields ['owner', 'group', 'mode']
+ db.create_unique('Reporting_fileperms', ['owner', 'group', 'mode'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'FilePerms', fields ['owner', 'group', 'mode']
+ db.delete_unique('Reporting_fileperms', ['owner', 'group', 'mode'])
+
+ # Renaming field 'FilePerms.mode' to 'FilePerms.perms'
+ db.rename_column('Reporting_fileperms', 'mode', 'perms')
+
+ if not settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
+ # Adding unique constraint on 'FilePerms', fields ['owner', 'perms', 'group']
+ db.create_unique('Reporting_fileperms', ['owner', 'perms', 'group'])
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ }
+ }
+
+ complete_apps = ['Reporting']
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0003_expand_hash_key.py b/src/lib/Bcfg2/Reporting/south_migrations/0003_expand_hash_key.py
new file mode 100644
index 000000000..2da1fa722
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0003_expand_hash_key.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Changing field 'FailureEntry.hash_key'
+ db.alter_column('Reporting_failureentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
+
+ # Changing field 'PackageEntry.hash_key'
+ db.alter_column('Reporting_packageentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
+
+ # Changing field 'ServiceEntry.hash_key'
+ db.alter_column('Reporting_serviceentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
+
+ # Changing field 'PathEntry.hash_key'
+ db.alter_column('Reporting_pathentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
+
+ # Changing field 'ActionEntry.hash_key'
+ db.alter_column('Reporting_actionentry', 'hash_key', self.gf('django.db.models.fields.BigIntegerField')())
+
+ def backwards(self, orm):
+
+ # Changing field 'FailureEntry.hash_key'
+ db.alter_column('Reporting_failureentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
+
+ # Changing field 'PackageEntry.hash_key'
+ db.alter_column('Reporting_packageentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
+
+ # Changing field 'ServiceEntry.hash_key'
+ db.alter_column('Reporting_serviceentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
+
+ # Changing field 'PathEntry.hash_key'
+ db.alter_column('Reporting_pathentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
+
+ # Changing field 'ActionEntry.hash_key'
+ db.alter_column('Reporting_actionentry', 'hash_key', self.gf('django.db.models.fields.IntegerField')())
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0004_profile_can_be_null.py b/src/lib/Bcfg2/Reporting/south_migrations/0004_profile_can_be_null.py
new file mode 100644
index 000000000..26a053b67
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0004_profile_can_be_null.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Changing field 'Interaction.profile'
+ db.alter_column('Reporting_interaction', 'profile_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['Reporting.Group']))
+
+ def backwards(self, orm):
+
+ # User chose to not deal with backwards NULL issues for 'Interaction.profile'
+ raise RuntimeError("Cannot reverse this migration. 'Interaction.profile' and its values cannot be restored.")
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0005_add_selinux_entry_support.py b/src/lib/Bcfg2/Reporting/south_migrations/0005_add_selinux_entry_support.py
new file mode 100644
index 000000000..d5f5d801a
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0005_add_selinux_entry_support.py
@@ -0,0 +1,485 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'SELoginEntry'
+ db.create_table('Reporting_seloginentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxuser', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SELoginEntry'])
+
+ # Adding model 'SEUserEntry'
+ db.create_table('Reporting_seuserentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('roles', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_roles', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('prefix', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_prefix', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEUserEntry'])
+
+ # Adding model 'SEBooleanEntry'
+ db.create_table('Reporting_sebooleanentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('value', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEBooleanEntry'])
+
+ # Adding model 'SENodeEntry'
+ db.create_table('Reporting_senodeentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('proto', self.gf('django.db.models.fields.CharField')(max_length=4)),
+ ))
+ db.send_create_signal('Reporting', ['SENodeEntry'])
+
+ # Adding model 'SEFcontextEntry'
+ db.create_table('Reporting_sefcontextentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ('filetype', self.gf('django.db.models.fields.CharField')(max_length=16)),
+ ))
+ db.send_create_signal('Reporting', ['SEFcontextEntry'])
+
+ # Adding model 'SEInterfaceEntry'
+ db.create_table('Reporting_seinterfaceentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEInterfaceEntry'])
+
+ # Adding model 'SEPermissiveEntry'
+ db.create_table('Reporting_sepermissiveentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEPermissiveEntry'])
+
+ # Adding model 'SEModuleEntry'
+ db.create_table('Reporting_semoduleentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('current_disabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ))
+ db.send_create_signal('Reporting', ['SEModuleEntry'])
+
+ # Adding model 'SEPortEntry'
+ db.create_table('Reporting_seportentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('current_selinuxtype', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['SEPortEntry'])
+
+ # Adding M2M table for field sebooleans on 'Interaction'
+ db.create_table('Reporting_interaction_sebooleans', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sebooleanentry', models.ForeignKey(orm['Reporting.sebooleanentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sebooleans', ['interaction_id', 'sebooleanentry_id'])
+
+ # Adding M2M table for field seports on 'Interaction'
+ db.create_table('Reporting_interaction_seports', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seportentry', models.ForeignKey(orm['Reporting.seportentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seports', ['interaction_id', 'seportentry_id'])
+
+ # Adding M2M table for field sefcontexts on 'Interaction'
+ db.create_table('Reporting_interaction_sefcontexts', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sefcontextentry', models.ForeignKey(orm['Reporting.sefcontextentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sefcontexts', ['interaction_id', 'sefcontextentry_id'])
+
+ # Adding M2M table for field senodes on 'Interaction'
+ db.create_table('Reporting_interaction_senodes', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('senodeentry', models.ForeignKey(orm['Reporting.senodeentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_senodes', ['interaction_id', 'senodeentry_id'])
+
+ # Adding M2M table for field selogins on 'Interaction'
+ db.create_table('Reporting_interaction_selogins', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seloginentry', models.ForeignKey(orm['Reporting.seloginentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_selogins', ['interaction_id', 'seloginentry_id'])
+
+ # Adding M2M table for field seusers on 'Interaction'
+ db.create_table('Reporting_interaction_seusers', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seuserentry', models.ForeignKey(orm['Reporting.seuserentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seusers', ['interaction_id', 'seuserentry_id'])
+
+ # Adding M2M table for field seinterfaces on 'Interaction'
+ db.create_table('Reporting_interaction_seinterfaces', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('seinterfaceentry', models.ForeignKey(orm['Reporting.seinterfaceentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_seinterfaces', ['interaction_id', 'seinterfaceentry_id'])
+
+ # Adding M2M table for field sepermissives on 'Interaction'
+ db.create_table('Reporting_interaction_sepermissives', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('sepermissiveentry', models.ForeignKey(orm['Reporting.sepermissiveentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_sepermissives', ['interaction_id', 'sepermissiveentry_id'])
+
+ # Adding M2M table for field semodules on 'Interaction'
+ db.create_table('Reporting_interaction_semodules', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('semoduleentry', models.ForeignKey(orm['Reporting.semoduleentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_semodules', ['interaction_id', 'semoduleentry_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'SELoginEntry'
+ db.delete_table('Reporting_seloginentry')
+
+ # Deleting model 'SEUserEntry'
+ db.delete_table('Reporting_seuserentry')
+
+ # Deleting model 'SEBooleanEntry'
+ db.delete_table('Reporting_sebooleanentry')
+
+ # Deleting model 'SENodeEntry'
+ db.delete_table('Reporting_senodeentry')
+
+ # Deleting model 'SEFcontextEntry'
+ db.delete_table('Reporting_sefcontextentry')
+
+ # Deleting model 'SEInterfaceEntry'
+ db.delete_table('Reporting_seinterfaceentry')
+
+ # Deleting model 'SEPermissiveEntry'
+ db.delete_table('Reporting_sepermissiveentry')
+
+ # Deleting model 'SEModuleEntry'
+ db.delete_table('Reporting_semoduleentry')
+
+ # Deleting model 'SEPortEntry'
+ db.delete_table('Reporting_seportentry')
+
+ # Removing M2M table for field sebooleans on 'Interaction'
+ db.delete_table('Reporting_interaction_sebooleans')
+
+ # Removing M2M table for field seports on 'Interaction'
+ db.delete_table('Reporting_interaction_seports')
+
+ # Removing M2M table for field sefcontexts on 'Interaction'
+ db.delete_table('Reporting_interaction_sefcontexts')
+
+ # Removing M2M table for field senodes on 'Interaction'
+ db.delete_table('Reporting_interaction_senodes')
+
+ # Removing M2M table for field selogins on 'Interaction'
+ db.delete_table('Reporting_interaction_selogins')
+
+ # Removing M2M table for field seusers on 'Interaction'
+ db.delete_table('Reporting_interaction_seusers')
+
+ # Removing M2M table for field seinterfaces on 'Interaction'
+ db.delete_table('Reporting_interaction_seinterfaces')
+
+ # Removing M2M table for field sepermissives on 'Interaction'
+ db.delete_table('Reporting_interaction_sepermissives')
+
+ # Removing M2M table for field semodules on 'Interaction'
+ db.delete_table('Reporting_interaction_semodules')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.sebooleanentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'Reporting.sefcontextentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seinterfaceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seloginentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
+ 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.semoduleentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
+ 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.senodeentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.sepermissiveentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seportentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ },
+ 'Reporting.seuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
+ 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0006_add_user_group_entry_support.py b/src/lib/Bcfg2/Reporting/south_migrations/0006_add_user_group_entry_support.py
new file mode 100644
index 000000000..d86e663d5
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0006_add_user_group_entry_support.py
@@ -0,0 +1,340 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'POSIXGroupEntry'
+ db.create_table('Reporting_posixgroupentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('current_gid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ))
+ db.send_create_signal('Reporting', ['POSIXGroupEntry'])
+
+ # Adding model 'POSIXUserEntry'
+ db.create_table('Reporting_posixuserentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
+ ('hash_key', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
+ ('state', self.gf('django.db.models.fields.IntegerField')()),
+ ('exists', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('current_uid', self.gf('django.db.models.fields.IntegerField')(null=True)),
+ ('group', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('current_group', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)),
+ ('gecos', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('current_gecos', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ('home', self.gf('django.db.models.fields.CharField')(max_length=1024)),
+ ('current_home', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ('shell', self.gf('django.db.models.fields.CharField')(default='/bin/bash', max_length=1024)),
+ ('current_shell', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)),
+ ))
+ db.send_create_signal('Reporting', ['POSIXUserEntry'])
+
+ # Adding M2M table for field posixusers on 'Interaction'
+ db.create_table('Reporting_interaction_posixusers', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('posixuserentry', models.ForeignKey(orm['Reporting.posixuserentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_posixusers', ['interaction_id', 'posixuserentry_id'])
+
+ # Adding M2M table for field posixgroups on 'Interaction'
+ db.create_table('Reporting_interaction_posixgroups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('interaction', models.ForeignKey(orm['Reporting.interaction'], null=False)),
+ ('posixgroupentry', models.ForeignKey(orm['Reporting.posixgroupentry'], null=False))
+ ))
+ db.create_unique('Reporting_interaction_posixgroups', ['interaction_id', 'posixgroupentry_id'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'POSIXGroupEntry'
+ db.delete_table('Reporting_posixgroupentry')
+
+ # Deleting model 'POSIXUserEntry'
+ db.delete_table('Reporting_posixuserentry')
+
+ # Removing M2M table for field posixusers on 'Interaction'
+ db.delete_table('Reporting_interaction_posixusers')
+
+ # Removing M2M table for field posixgroups on 'Interaction'
+ db.delete_table('Reporting_interaction_posixgroups')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'posixgroups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXGroupEntry']", 'symmetrical': 'False'}),
+ 'posixusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXUserEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.posixgroupentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXGroupEntry'},
+ 'current_gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.posixuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXUserEntry'},
+ 'current_gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_group': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'current_home': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_shell': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'home': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'shell': ('django.db.models.fields.CharField', [], {'default': "'/bin/bash'", 'max_length': '1024'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+ },
+ 'Reporting.sebooleanentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'Reporting.sefcontextentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seinterfaceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seloginentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
+ 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.semoduleentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
+ 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.senodeentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.sepermissiveentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seportentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ },
+ 'Reporting.seuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
+ 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/0007_add_flag_fields_interaction.py b/src/lib/Bcfg2/Reporting/south_migrations/0007_add_flag_fields_interaction.py
new file mode 100644
index 000000000..491ecb845
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/0007_add_flag_fields_interaction.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Interaction.dry_run'
+ db.add_column('Reporting_interaction', 'dry_run',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Interaction.only_important'
+ db.add_column('Reporting_interaction', 'only_important',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Interaction.dry_run'
+ db.delete_column('Reporting_interaction', 'dry_run')
+
+ # Deleting field 'Interaction.only_important'
+ db.delete_column('Reporting_interaction', 'only_important')
+
+
+ models = {
+ 'Reporting.actionentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ActionEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'output': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'check'", 'max_length': '128'})
+ },
+ 'Reporting.bundle': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Bundle'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'Reporting.client': {
+ 'Meta': {'object_name': 'Client'},
+ 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'current_interaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'parent_client'", 'null': 'True', 'to': "orm['Reporting.Interaction']"}),
+ 'expiration': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.deviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'DeviceEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'current_minor': ('django.db.models.fields.IntegerField', [], {}),
+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_major': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_minor': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.failureentry': {
+ 'Meta': {'object_name': 'FailureEntry'},
+ 'entry_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'message': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileacl': {
+ 'Meta': {'object_name': 'FileAcl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
+ },
+ 'Reporting.fileperms': {
+ 'Meta': {'unique_together': "(('owner', 'group', 'mode'),)", 'object_name': 'FilePerms'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mode': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'Reporting.group': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Group'},
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'category': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'profile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'Reporting.interaction': {
+ 'Meta': {'ordering': "['-timestamp']", 'unique_together': "(('client', 'timestamp'),)", 'object_name': 'Interaction'},
+ 'actions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ActionEntry']", 'symmetrical': 'False'}),
+ 'bad_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'bundles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Bundle']", 'symmetrical': 'False'}),
+ 'client': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'interactions'", 'to': "orm['Reporting.Client']"}),
+ 'dry_run': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'extra_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'failures': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FailureEntry']", 'symmetrical': 'False'}),
+ 'good_count': ('django.db.models.fields.IntegerField', [], {}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.Group']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'only_important': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PackageEntry']", 'symmetrical': 'False'}),
+ 'paths': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.PathEntry']", 'symmetrical': 'False'}),
+ 'posixgroups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXGroupEntry']", 'symmetrical': 'False'}),
+ 'posixusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.POSIXUserEntry']", 'symmetrical': 'False'}),
+ 'profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['Reporting.Group']"}),
+ 'repo_rev_code': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'sebooleans': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEBooleanEntry']", 'symmetrical': 'False'}),
+ 'sefcontexts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEFcontextEntry']", 'symmetrical': 'False'}),
+ 'seinterfaces': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEInterfaceEntry']", 'symmetrical': 'False'}),
+ 'selogins': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SELoginEntry']", 'symmetrical': 'False'}),
+ 'semodules': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEModuleEntry']", 'symmetrical': 'False'}),
+ 'senodes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SENodeEntry']", 'symmetrical': 'False'}),
+ 'sepermissives': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPermissiveEntry']", 'symmetrical': 'False'}),
+ 'seports': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEPortEntry']", 'symmetrical': 'False'}),
+ 'server': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+ 'services': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.ServiceEntry']", 'symmetrical': 'False'}),
+ 'seusers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.SEUserEntry']", 'symmetrical': 'False'}),
+ 'state': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'total_count': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.linkentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'LinkEntry', '_ormbases': ['Reporting.PathEntry']},
+ 'current_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
+ 'pathentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['Reporting.PathEntry']", 'unique': 'True', 'primary_key': 'True'}),
+ 'target_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'})
+ },
+ 'Reporting.packageentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PackageEntry'},
+ 'current_version': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
+ 'verification_details': ('django.db.models.fields.TextField', [], {'default': "''"})
+ },
+ 'Reporting.pathentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'PathEntry'},
+ 'acls': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['Reporting.FileAcl']", 'symmetrical': 'False'}),
+ 'current_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"}),
+ 'detail_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'details': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'path_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_perms': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['Reporting.FilePerms']"})
+ },
+ 'Reporting.performance': {
+ 'Meta': {'object_name': 'Performance'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interaction': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'performance_items'", 'to': "orm['Reporting.Interaction']"}),
+ 'metric': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'value': ('django.db.models.fields.DecimalField', [], {'max_digits': '32', 'decimal_places': '16'})
+ },
+ 'Reporting.posixgroupentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXGroupEntry'},
+ 'current_gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.posixuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'POSIXUserEntry'},
+ 'current_gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_group': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+ 'current_home': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_shell': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+ 'current_uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'gecos': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'home': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'shell': ('django.db.models.fields.CharField', [], {'default': "'/bin/bash'", 'max_length': '1024'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'uid': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+ },
+ 'Reporting.sebooleanentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEBooleanEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'value': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'Reporting.sefcontextentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEFcontextEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seinterfaceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEInterfaceEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seloginentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SELoginEntry'},
+ 'current_selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxuser': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.semoduleentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEModuleEntry'},
+ 'current_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.senodeentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SENodeEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'proto': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.sepermissiveentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPermissiveEntry'},
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.seportentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEPortEntry'},
+ 'current_selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'selinuxtype': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'Reporting.serviceentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'ServiceEntry'},
+ 'current_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {}),
+ 'target_status': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'})
+ },
+ 'Reporting.seuserentry': {
+ 'Meta': {'ordering': "('state', 'name')", 'object_name': 'SEUserEntry'},
+ 'current_prefix': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'current_roles': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'exists': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'hash_key': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
+ 'prefix': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'state': ('django.db.models.fields.IntegerField', [], {})
+ }
+ }
+
+ complete_apps = ['Reporting'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Reporting/south_migrations/__init__.py b/src/lib/Bcfg2/Reporting/south_migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/south_migrations/__init__.py
diff --git a/src/lib/Bcfg2/Reporting/templates/base-timeview.html b/src/lib/Bcfg2/Reporting/templates/base-timeview.html
index 9a5ef651c..28a9fa0f4 100644
--- a/src/lib/Bcfg2/Reporting/templates/base-timeview.html
+++ b/src/lib/Bcfg2/Reporting/templates/base-timeview.html
@@ -17,7 +17,7 @@ function bcfg2_check_date() {
}
document.write(getCalendarStyles());
</script>
-{% if not timestamp %}Rendered at {% now "Y-m-d H:i" %} | {% else %}View as of {{ timestamp|date:"Y-m-d H:i" }} | {% endif %}{% spaceless %}
+{% if not timestamp %}Rendered at {% now "SHORT_DATETIME_FORMAT" %} | {% else %}View as of {{ timestamp|date:"SHORT_DATETIME_FORMAT" }} | {% endif %}{% spaceless %}
<a id='cal_link' name='cal_link' href='#' onclick='showCalendar(); return false;'
>[change]</a>
<form method='post' action='{{ path }}' id='cal_form' name='cal_form'>
diff --git a/src/lib/Bcfg2/Reporting/templates/base.html b/src/lib/Bcfg2/Reporting/templates/base.html
index 8b197231c..2e9ec888a 100644
--- a/src/lib/Bcfg2/Reporting/templates/base.html
+++ b/src/lib/Bcfg2/Reporting/templates/base.html
@@ -1,9 +1,5 @@
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
-
+{% load url from bcfg2_compat %}
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
@@ -93,7 +89,7 @@ This is needed for Django versions less than 1.5
<div style='clear:both'></div>
</div><!-- document -->
<div id="footer">
- <span>Bcfg2 Version 1.4.0pre1</span>
+ <span>Bcfg2 Version 1.4.0pre2</span>
</div>
<div id="calendar_div" style='position:absolute; visibility:hidden; background-color:white; layer-background-color:white;'></div>
diff --git a/src/lib/Bcfg2/Reporting/templates/clients/detail.html b/src/lib/Bcfg2/Reporting/templates/clients/detail.html
index e890589a7..5a638f2b1 100644
--- a/src/lib/Bcfg2/Reporting/templates/clients/detail.html
+++ b/src/lib/Bcfg2/Reporting/templates/clients/detail.html
@@ -1,9 +1,6 @@
{% extends "base.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Client {{client.name}}{% endblock %}
@@ -39,7 +36,7 @@ span.history_links a {
<select id="quick" name="quick" onchange="javascript:pageJump('quick');">
<option value="" selected="selected">--- Time ---</option>
{% for i in client.interactions.all|slice:":25" %}
- <option value="{% url "reports_client_detail_pk" hostname=client.name pk=i.id %}">{{i.timestamp|date:"c"}}</option>
+ <option value="{% url "reports_client_detail_pk" hostname=client.name pk=i.id %}">{{i.timestamp|date:"DATETIME_FORMAT"}}</option>
{% endfor %}
</select></span>
</div>
@@ -76,7 +73,7 @@ span.history_links a {
</div>
<table id='groups_table' class='entry_list' style='display: none'>
{% endif %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td class='entry_list_type'>{{group}}</td>
</tr>
{% if forloop.last %}
@@ -90,11 +87,11 @@ span.history_links a {
<div class='entry_list'>
<div class='entry_list_head' onclick='javascript:toggleMe("bundles_table");'>
<h3>Bundle membership</h3>
- <div class='entry_expand_tab' id='plusminus_bundless_table'>[+]</div>
+ <div class='entry_expand_tab' id='plusminus_bundles_table'>[+]</div>
</div>
<table id='bundles_table' class='entry_list' style='display: none'>
{% endif %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td class='entry_list_type'>{{bundle}}</td>
</tr>
{% if forloop.last %}
@@ -112,7 +109,7 @@ span.history_links a {
</div>
<table id='{{entry_type}}_table' class='entry_list'>
{% for entry in entry_list %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td class='entry_list_type'>{{entry.entry_type}}</td>
<td><a href="{% url "reports_item" entry.class_name entry.pk interaction.pk %}">
{{entry.name}}</a></td>
@@ -127,11 +124,11 @@ span.history_links a {
<div class='entry_list'>
<div class='entry_list_head failed-lineitem' onclick='javascript:toggleMe("failures_table");'>
<h3>Failed Entries &#8212; {{ interaction.failures.all|length }}</h3>
- <div class='entry_expand_tab' id='plusminus_failuress_table'>[+]</div>
+ <div class='entry_expand_tab' id='plusminus_failures_table'>[+]</div>
</div>
<table id='failures_table' class='entry_list' style='display: none'>
{% for failure in interaction.failures.all %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td class='entry_list_type'>{{failure.entry_type}}</td>
<td><a href="{% url "reports_item" failure.class_name failure.pk interaction.pk %}">
{{failure.name}}</a></td>
diff --git a/src/lib/Bcfg2/Reporting/templates/clients/detailed-list.html b/src/lib/Bcfg2/Reporting/templates/clients/detailed-list.html
index 6a314bd88..65c53a76c 100644
--- a/src/lib/Bcfg2/Reporting/templates/clients/detailed-list.html
+++ b/src/lib/Bcfg2/Reporting/templates/clients/detailed-list.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Detailed Client Listing{% endblock %}
{% block pagebanner %}Clients - Detailed View{% endblock %}
@@ -24,15 +21,17 @@ This is needed for Django versions less than 1.5
<td class='right_column_wide'>{% sort_link 'server' 'Server' %}</td>
</tr>
{% for entry in entry_list %}
- <tr class='{% cycle listview,listview_alt %}'>
- <td class='left_column'><a href='{% url "Bcfg2.Reporting.views.client_detail" hostname=entry.client.name pk=entry.id %}'>{{ entry.client.name }}</a></td>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
+ <td class='left_column'><a href='{% url "reports_client_detail_pk" hostname=entry.client.name pk=entry.id %}'>{{ entry.client.name }}</a></td>
<td class='right_column' style='width:75px'><a href='{% add_url_filter state=entry.state %}'
class='{{entry|determine_client_state}}'>{{ entry.state }}</a></td>
<td class='right_column_narrow'>{{ entry.good_count }}</td>
<td class='right_column_narrow'>{{ entry.bad_count }}</td>
<td class='right_column_narrow'>{{ entry.modified_count }}</td>
<td class='right_column_narrow'>{{ entry.extra_count }}</td>
- <td class='right_column'><span {% if entry.isstale %}class='dirty-lineitem'{% endif %}>{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }}</span></td>
+ <td class='right_column'><span {% if entry.isstale %}class='dirty-lineitem'{% endif %} style="white-space: nowrap;">
+ {{ entry.timestamp|date:"SHORT_DATETIME_FORMAT"|safe }}
+ </span></td>
<td class='right_column_wide'>
{% if entry.server %}
<a href='{% add_url_filter server=entry.server %}'>{{ entry.server }}</a>
diff --git a/src/lib/Bcfg2/Reporting/templates/clients/index.html b/src/lib/Bcfg2/Reporting/templates/clients/index.html
index eba83670b..1eaa3ca9b 100644
--- a/src/lib/Bcfg2/Reporting/templates/clients/index.html
+++ b/src/lib/Bcfg2/Reporting/templates/clients/index.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block extra_header_info %}
{% endblock%}
diff --git a/src/lib/Bcfg2/Reporting/templates/clients/manage.html b/src/lib/Bcfg2/Reporting/templates/clients/manage.html
index 03918aad7..3f080c52e 100644
--- a/src/lib/Bcfg2/Reporting/templates/clients/manage.html
+++ b/src/lib/Bcfg2/Reporting/templates/clients/manage.html
@@ -1,8 +1,5 @@
{% extends "base.html" %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block extra_header_info %}
{% endblock%}
@@ -24,7 +21,7 @@ This is needed for Django versions less than 1.5
<td class='right_column_narrow'>Manage</td>
</tr>
{% for client in clients %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td><span id="{{ client.name }}"> </span>
<span id="ttag-{{ client.name }}"> </span>
<span id="s-ttag-{{ client.name }}"> </span>
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/common.html b/src/lib/Bcfg2/Reporting/templates/config_items/common.html
index 91f37d7dc..8c2df8db1 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/common.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/common.html
@@ -1,6 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Common Problems{% endblock %}
@@ -28,7 +28,7 @@
<table id='table_{{ type_name }}' class='entry_list'>
<tr style='text-align: left'><th>Type</th><th>Name</th><th>Count</th><th>Reason</th></tr>
{% for item in type_list %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td>{{ item.ENTRY_TYPE }}</td>
<td><a href='{% url "reports_entry" item.class_name item.pk %}'>{{ item.name }}</a></td>
<td>{{ item.num_entries }}</td>
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/entry_status.html b/src/lib/Bcfg2/Reporting/templates/config_items/entry_status.html
index e3befb0eb..4d9c42391 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/entry_status.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/entry_status.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Entry Status{% endblock %}
@@ -20,9 +17,11 @@ This is needed for Django versions less than 1.5
<tr style='text-align: left' ><th>Name</th><th>Timestamp</th><th>State</th><th>Reason</th></tr>
{% for item, inters in items %}
{% for inter in inters %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td><a href='{% url "reports_client_detail" hostname=inter.client.name %}'>{{inter.client.name}}</a></td>
- <td><a href='{% url "reports_client_detail_pk" hostname=inter.client.name pk=inter.pk %}'>{{inter.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe}}</a></td>
+ <td><a href='{% url "reports_client_detail_pk" hostname=inter.client.name pk=inter.pk %}' style="white-space: nowrap;">
+ {{inter.timestamp|date:"SHORT_DATETIME_FORMAT"|safe}}
+ </a></td>
<td>{{ item.get_state_display }}</td>
<td style='white-space: nowrap'><a href='{% url "reports_item" entry_type=item.class_name pk=item.pk %}'>({{item.pk}}) {{item.short_list|join:","}}</a></td>
</tr>
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/item.html b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
index c6e6df020..91c368bd7 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/item.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/item.html
@@ -1,11 +1,7 @@
{% extends "base.html" %}
{% load split %}
{% load syntax_coloring %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
-
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Element Details{% endblock %}
@@ -130,7 +126,7 @@ div.entry_list h3 {
<div class='entry_list'>
<div class='entry_list_head'>
- <h3>Occurences on {{ timestamp|date:"Y-m-d" }}</h3>
+ <h3>Occurences on {{ timestamp|date:"SHORT_DATE_FORMAT" }}</h3>
</div>
{% if associated_list %}
<table class="entry_list" cellpadding="3">
diff --git a/src/lib/Bcfg2/Reporting/templates/config_items/listing.html b/src/lib/Bcfg2/Reporting/templates/config_items/listing.html
index 0e4812e85..b8fba61ce 100644
--- a/src/lib/Bcfg2/Reporting/templates/config_items/listing.html
+++ b/src/lib/Bcfg2/Reporting/templates/config_items/listing.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Element Listing{% endblock %}
@@ -24,7 +21,7 @@ This is needed for Django versions less than 1.5
<table id='table_{{ type_name }}' class='entry_list'>
<tr style='text-align: left' ><th>Name</th><th>Count</th><th>Reason</th></tr>
{% for entry in type_data %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td><a href='{% url "reports_entry" entry.class_name entry.pk %}'>{{entry.name}}</a></td>
<td>{{entry.num_entries}}</td>
<td><a href='{% url "reports_item" entry.class_name entry.pk %}'>{{entry.short_list|join:","}}</a></td>
diff --git a/src/lib/Bcfg2/Reporting/templates/displays/summary.html b/src/lib/Bcfg2/Reporting/templates/displays/summary.html
index ffafd52e0..a9082aeee 100644
--- a/src/lib/Bcfg2/Reporting/templates/displays/summary.html
+++ b/src/lib/Bcfg2/Reporting/templates/displays/summary.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Client Summary{% endblock %}
{% block pagebanner %}Clients - Summary{% endblock %}
@@ -33,7 +30,7 @@ hide_tables[{{ forloop.counter0 }}] = "table_{{ summary.name }}";
<table id='table_{{ summary.name }}' class='entry_list'>
{% for node in summary.nodes|sort_interactions_by_name %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td><a href='{% url "reports_client_detail_pk" hostname=node.client.name pk=node.id %}'>{{ node.client.name }}</a></td>
</tr>
{% endfor %}
diff --git a/src/lib/Bcfg2/Reporting/templates/displays/timing.html b/src/lib/Bcfg2/Reporting/templates/displays/timing.html
index 8ac5e49bb..69d56b62f 100644
--- a/src/lib/Bcfg2/Reporting/templates/displays/timing.html
+++ b/src/lib/Bcfg2/Reporting/templates/displays/timing.html
@@ -1,9 +1,6 @@
{% extends "base-timeview.html" %}
{% load bcfg2_tags %}
-{% comment %}
-This is needed for Django versions less than 1.5
-{% endcomment %}
-{% load url from future %}
+{% load url from bcfg2_compat %}
{% block title %}Bcfg2 - Performance Metrics{% endblock %}
{% block pagebanner %}Performance Metrics{% endblock %}
@@ -26,7 +23,7 @@ This is needed for Django versions less than 1.5
<td>Total</td>
</tr>
{% for metric in metrics|dictsort:"name" %}
- <tr class='{% cycle listview,listview_alt %}'>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
<td><a style='font-size: 100%'
href='{% url "reports_client_detail" hostname=metric.name %}'>{{ metric.name }}</a></td>
{% for mitem in metric|build_metric_list %}
diff --git a/src/lib/Bcfg2/Reporting/templates/widgets/interaction_list.inc b/src/lib/Bcfg2/Reporting/templates/widgets/interaction_list.inc
index 30ed2fd3e..1dbcbf3f1 100644
--- a/src/lib/Bcfg2/Reporting/templates/widgets/interaction_list.inc
+++ b/src/lib/Bcfg2/Reporting/templates/widgets/interaction_list.inc
@@ -14,8 +14,10 @@
<td class='right_column_wide'>Server</td>
</tr>
{% for entry in entry_list %}
- <tr class='{% cycle listview,listview_alt %}'>
- <td class='left_column'><a href='{% url reports_client_detail_pk hostname=entry.client.name, pk=entry.id %}'>{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }}</a></td>
+ <tr class='{% cycle 'listview' 'listview_alt' %}'>
+ <td class='left_column'><a href='{% url "reports_client_detail_pk" hostname=entry.client.name pk=entry.id %}' class="white-space: nowrap;">
+ {{ entry.timestamp|date:"SHORT_DATETIME_FORMAT"|safe }}
+ </a></td>
{% if not client %}
<td class='right_column_wide'><a href='{% add_url_filter hostname=entry.client.name %}'>{{ entry.client.name }}</a></td>
{% endif %}
diff --git a/src/lib/Bcfg2/Reporting/templatetags/bcfg2_compat.py b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_compat.py
new file mode 100644
index 000000000..da4c73745
--- /dev/null
+++ b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_compat.py
@@ -0,0 +1,14 @@
+from django.template import Library
+
+try:
+ from django.templatetags.future import url as django_url
+except ImportError:
+ # future is removed in django 1.9
+ from django.template.defaulttags import url as django_url
+
+register = Library()
+
+
+@register.tag
+def url(parser, token):
+ return django_url(parser, token)
diff --git a/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
index 09aebc7fd..5c50c614d 100644
--- a/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
+++ b/src/lib/Bcfg2/Reporting/templatetags/bcfg2_tags.py
@@ -1,11 +1,11 @@
import sys
from copy import copy
+import django
from django import template
from django.conf import settings
from django.core.urlresolvers import resolve, reverse, \
Resolver404, NoReverseMatch
-from django.template.loader import get_template_from_string
from django.utils.encoding import smart_str
from django.utils.safestring import mark_safe
from datetime import datetime, timedelta
@@ -394,6 +394,13 @@ class SortLinkNode(template.Node):
self.sort_key = template.Variable(sort_key)
self.text = template.Variable(text)
+ def _render_template(self, context):
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 8:
+ return context.template.engine.from_string(self.__TMPL__)
+ else:
+ from django.template.loader import get_template_from_string
+ return get_template_from_string(self.__TMPL__).render(context)
+
def render(self, context):
try:
try:
@@ -419,7 +426,7 @@ class SortLinkNode(template.Node):
context.push()
context['key'] = sort_key
context['text'] = mark_safe(text)
- output = get_template_from_string(self.__TMPL__).render(context)
+ output = self._render_template(context)
context.pop()
return output
except:
diff --git a/src/lib/Bcfg2/Reporting/urls.py b/src/lib/Bcfg2/Reporting/urls.py
index 3a40cb932..1ff955cad 100644
--- a/src/lib/Bcfg2/Reporting/urls.py
+++ b/src/lib/Bcfg2/Reporting/urls.py
@@ -2,6 +2,7 @@ from Bcfg2.Reporting.Compat import url, patterns # django compat imports
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import HttpResponsePermanentRedirect
from Bcfg2.Reporting.utils import filteredUrls, paginatedUrls, timeviewUrls
+from Bcfg2.Reporting import views
handler500 = 'Bcfg2.Reporting.views.server_error'
@@ -12,52 +13,39 @@ def newRoot(request):
grid_view = '/grid'
return HttpResponsePermanentRedirect(grid_view)
-urlpatterns = patterns('Bcfg2.Reporting',
+urlpatterns = patterns('',
(r'^$', newRoot),
- url(r'^manage/?$', 'views.client_manage', name='reports_client_manage'),
- url(r'^client/(?P<hostname>[^/]+)/(?P<pk>\d+)/?$', 'views.client_detail', name='reports_client_detail_pk'),
- url(r'^client/(?P<hostname>[^/]+)/?$', 'views.client_detail', name='reports_client_detail'),
- url(r'^element/(?P<entry_type>\w+)/(?P<pk>\d+)/(?P<interaction>\d+)?/?$', 'views.config_item', name='reports_item'),
- url(r'^element/(?P<entry_type>\w+)/(?P<pk>\d+)/?$', 'views.config_item', name='reports_item'),
- url(r'^entry/(?P<entry_type>\w+)/(?P<pk>\w+)/?$', 'views.entry_status', name='reports_entry'),
+ url(r'^manage/?$', views.client_manage, name='reports_client_manage'),
+ url(r'^client/(?P<hostname>[^/]+)/(?P<pk>\d+)/?$', views.client_detail, name='reports_client_detail_pk'),
+ url(r'^client/(?P<hostname>[^/]+)/?$', views.client_detail, name='reports_client_detail'),
+ url(r'^element/(?P<entry_type>\w+)/(?P<pk>\d+)/(?P<interaction>\d+)?/?$', views.config_item, name='reports_item'),
+ url(r'^element/(?P<entry_type>\w+)/(?P<pk>\d+)/?$', views.config_item, name='reports_item'),
+ url(r'^entry/(?P<entry_type>\w+)/(?P<pk>\w+)/?$', views.entry_status, name='reports_entry'),
)
-urlpatterns += patterns('Bcfg2.Reporting',
+urlpatterns += patterns('',
*timeviewUrls(
- (r'^summary/?$', 'views.display_summary', None, 'reports_summary'),
- (r'^timing/?$', 'views.display_timing', None, 'reports_timing'),
- (r'^common/group/(?P<group>[^/]+)/(?P<threshold>\d+)/?$', 'views.common_problems', None, 'reports_common_problems'),
- (r'^common/group/(?P<group>[^/]+)+/?$', 'views.common_problems', None, 'reports_common_problems'),
- (r'^common/(?P<threshold>\d+)/?$', 'views.common_problems', None, 'reports_common_problems'),
- (r'^common/?$', 'views.common_problems', None, 'reports_common_problems'),
+ (r'^summary/?$', views.display_summary, None, 'reports_summary'),
+ (r'^timing/?$', views.display_timing, None, 'reports_timing'),
+ (r'^common/group/(?P<group>[^/]+)/(?P<threshold>\d+)/?$', views.common_problems, None, 'reports_common_problems'),
+ (r'^common/group/(?P<group>[^/]+)+/?$', views.common_problems, None, 'reports_common_problems'),
+ (r'^common/(?P<threshold>\d+)/?$', views.common_problems, None, 'reports_common_problems'),
+ (r'^common/?$', views.common_problems, None, 'reports_common_problems'),
))
-urlpatterns += patterns('Bcfg2.Reporting',
+urlpatterns += patterns('',
*filteredUrls(*timeviewUrls(
- (r'^grid/?$', 'views.client_index', None, 'reports_grid_view'),
+ (r'^grid/?$', views.client_index, None, 'reports_grid_view'),
(r'^detailed/?$',
- 'views.client_detailed_list', None, 'reports_detailed_list'),
- (r'^elements/(?P<item_state>\w+)/?$', 'views.config_item_list', None, 'reports_item_list'),
+ views.client_detailed_list, None, 'reports_detailed_list'),
+ (r'^elements/(?P<item_state>\w+)/?$', views.config_item_list, None, 'reports_item_list'),
)))
-urlpatterns += patterns('Bcfg2.Reporting',
+urlpatterns += patterns('',
*paginatedUrls( *filteredUrls(
(r'^history/?$',
- 'views.render_history_view', None, 'reports_history'),
+ views.render_history_view, None, 'reports_history'),
(r'^history/(?P<hostname>[^/|]+)/?$',
- 'views.render_history_view', None, 'reports_client_history'),
+ views.render_history_view, None, 'reports_client_history'),
)))
-
- # Uncomment this for admin:
- #(r'^admin/', include('django.contrib.admin.urls')),
-
-
-## Uncomment this section if using authentication
-#urlpatterns += patterns('',
-# (r'^login/$', 'django.contrib.auth.views.login',
-# {'template_name': 'auth/login.html'}),
-# (r'^logout/$', 'django.contrib.auth.views.logout',
-# {'template_name': 'auth/logout.html'})
-# )
-
diff --git a/src/lib/Bcfg2/Reporting/views.py b/src/lib/Bcfg2/Reporting/views.py
index 0b8ed65cc..3b9e2e53b 100644
--- a/src/lib/Bcfg2/Reporting/views.py
+++ b/src/lib/Bcfg2/Reporting/views.py
@@ -7,10 +7,10 @@ from datetime import datetime, timedelta
import sys
from time import strptime
-from django.template import Context, RequestContext
+from django.template import Context
from django.http import \
HttpResponse, HttpResponseRedirect, HttpResponseServerError, Http404
-from django.shortcuts import render_to_response, get_object_or_404
+from django.shortcuts import render, get_object_or_404
from django.core.urlresolvers import \
resolve, reverse, Resolver404, NoReverseMatch
from django.db import DatabaseError
@@ -166,11 +166,10 @@ def config_item(request, pk, entry_type, interaction=None):
template = 'config_items/item-failure.html'
else:
template = 'config_items/item.html'
- return render_to_response(template,
- {'item': item,
- 'associated_list': associated_list,
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
+ return render(request, template,
+ {'item': item,
+ 'associated_list': associated_list,
+ 'timestamp': timestamp})
@timeview
@@ -186,16 +185,15 @@ def config_item_list(request, item_state, timestamp=None, **kwargs):
lists = []
for etype in ENTRY_TYPES:
ldata = etype.objects.filter(state=state, interaction__in=current_clients)\
- .annotate(num_entries=Count('id')).select_related('linkentry', 'target_perms', 'current_perms')
+ .annotate(num_entries=Count('id')).select_related()
if len(ldata) > 0:
# Property doesn't render properly..
lists.append((etype.ENTRY_TYPE, ldata))
- return render_to_response('config_items/listing.html',
- {'item_list': lists,
- 'item_state': item_state,
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
+ return render(request, 'config_items/listing.html',
+ {'item_list': lists,
+ 'item_state': item_state,
+ 'timestamp': timestamp})
@timeview
@@ -219,12 +217,10 @@ def entry_status(request, entry_type, pk, timestamp=None, **kwargs):
items.append((it, it.interaction_set.filter(pk__in=current_clients).order_by('client__name').select_related('client')))
seen.append(it.pk)
- return render_to_response('config_items/entry_status.html',
- {'entry': item,
- 'items': items,
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
-
+ return render(request, 'config_items/entry_status.html',
+ {'entry': item,
+ 'items': items,
+ 'timestamp': timestamp})
@timeview
def common_problems(request, timestamp=None, threshold=None, group=None):
@@ -262,11 +258,10 @@ def common_problems(request, timestamp=None, threshold=None, group=None):
# Property doesn't render properly..
lists.append((etype.ENTRY_TYPE, ldata))
- return render_to_response('config_items/common.html',
- {'lists': lists,
- 'timestamp': timestamp,
- 'threshold': threshold},
- context_instance=RequestContext(request))
+ return render(request, 'config_items/common.html',
+ {'lists': lists,
+ 'timestamp': timestamp,
+ 'threshold': threshold})
@timeview
@@ -281,10 +276,9 @@ def client_index(request, timestamp=None, **kwargs):
list = _handle_filters(Interaction.objects.recent(timestamp), **kwargs).\
select_related('client').order_by("client__name").all()
- return render_to_response('clients/index.html',
- {'inter_list': list,
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
+ return render(request, 'clients/index.html',
+ {'inter_list': list,
+ 'timestamp': timestamp})
@timeview
@@ -374,9 +368,9 @@ def client_manage(request):
client_name = "<none>"
message = "Couldn't find client \"%s\"" % client_name
- return render_to_response('clients/manage.html',
- {'clients': Client.objects.order_by('name').all(), 'message': message},
- context_instance=RequestContext(request))
+ return render(request, 'clients/manage.html',
+ {'clients': Client.objects.order_by('name').all(),
+ 'message': message})
@timeview
@@ -429,10 +423,10 @@ def display_summary(request, timestamp=None):
summary_data.append(get_dict('stale',
'nodes did not run within the last 24 hours.'))
- return render_to_response('displays/summary.html',
- {'summary_data': summary_data, 'node_count': node_count,
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
+ return render(request, 'displays/summary.html',
+ {'summary_data': summary_data,
+ 'node_count': node_count,
+ 'timestamp': timestamp})
@timeview
@@ -447,10 +441,9 @@ def display_timing(request, timestamp=None):
mdict[client] = { 'name': client }
mdict[client][perf.metric] = perf.value
- return render_to_response('displays/timing.html',
- {'metrics': list(mdict.values()),
- 'timestamp': timestamp},
- context_instance=RequestContext(request))
+ return render(request, 'displays/timing.html',
+ {'metrics': list(mdict.values()),
+ 'timestamp': timestamp})
def render_history_view(request, template='clients/history.html', **kwargs):
@@ -525,8 +518,7 @@ def render_history_view(request, template='clients/history.html', **kwargs):
else:
context['entry_list'] = iquery.all()
- return render_to_response(template, context,
- context_instance=RequestContext(request))
+ return render(request, template, context)
def prepare_paginated_list(request, context, paged_list, page=1, max_results=25):
diff --git a/src/lib/Bcfg2/Server/Admin.py b/src/lib/Bcfg2/Server/Admin.py
index c294e6be5..77bca88eb 100644
--- a/src/lib/Bcfg2/Server/Admin.py
+++ b/src/lib/Bcfg2/Server/Admin.py
@@ -25,15 +25,19 @@ import Bcfg2.Server.Plugins.Metadata
try:
from django.core.exceptions import ImproperlyConfigured
from django.core import management
+ import django
import django.conf
import Bcfg2.Server.models
HAS_DJANGO = True
- try:
- import south # pylint: disable=W0611
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
HAS_REPORTS = True
- except ImportError:
- HAS_REPORTS = False
+ else:
+ try:
+ import south # pylint: disable=W0611
+ HAS_REPORTS = True
+ except ImportError:
+ HAS_REPORTS = False
except ImportError:
HAS_DJANGO = False
HAS_REPORTS = False
@@ -439,6 +443,25 @@ class Compare(AdminCmd):
print("")
+class ExpireCache(_ProxyAdminCmd):
+ """ Expire the metadata cache """
+
+ options = _ProxyAdminCmd.options + [
+ Bcfg2.Options.PositionalArgument(
+ "hostname", nargs="*", default=[],
+ help="Expire cache for the given host(s)")]
+
+ def run(self, setup):
+ clients = None
+ if setup.hostname is not None and len(setup.hostname) > 0:
+ clients = setup.hostname
+
+ try:
+ self.proxy.expire_metadata_cache(clients)
+ except Bcfg2.Client.Proxy.ProxyError:
+ self.errExit("Proxy Error: %s" % sys.exc_info()[1])
+
+
class Init(AdminCmd):
"""Interactively initialize a new repository."""
@@ -643,7 +666,7 @@ bcfg2 = %s
def create_key(self):
"""Creates a bcfg2.key at the directory specifed by keypath."""
cmd = Executor(timeout=120)
- subject = "/C=%s/ST=%s/L=%s/CN=%s'" % (
+ subject = "/C=%s/ST=%s/L=%s/CN=%s" % (
self.data['country'], self.data['state'], self.data['location'],
self.data['shostname'])
key = cmd.run(["openssl", "req", "-batch", "-x509", "-nodes",
@@ -877,6 +900,7 @@ if HAS_DJANGO:
Django management system """
command = None
args = []
+ kwargs = {}
def run(self, _):
'''Call a django command'''
@@ -885,7 +909,7 @@ if HAS_DJANGO:
else:
command = self.__class__.__name__.lower()
args = [command] + self.args
- management.call_command(*args)
+ management.call_command(*args, **self.kwargs)
class DBShell(_DjangoProxyCmd):
""" Call the Django 'dbshell' command on the database """
@@ -901,7 +925,6 @@ if HAS_DJANGO:
""" Sync the Django ORM with the configured database """
def run(self, setup):
- Bcfg2.Server.models.load_models()
try:
Bcfg2.DBSettings.sync_databases(
interactive=False,
@@ -915,6 +938,17 @@ if HAS_DJANGO:
self.logger.error("Database update failed: %s" % err)
raise SystemExit(1)
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ class Makemigrations(_DjangoProxyCmd):
+ """ Call the 'makemigrations' command on the database """
+ args = ['Reporting']
+
+ else:
+ class Schemamigration(_DjangoProxyCmd):
+ """ Call the South 'schemamigration' command on the database """
+ args = ['Bcfg2.Reporting']
+ kwargs = {'auto': True}
+
if HAS_REPORTS:
import datetime
diff --git a/src/lib/Bcfg2/Server/BuiltinCore.py b/src/lib/Bcfg2/Server/BuiltinCore.py
index e138c57e4..dc5cc46fb 100644
--- a/src/lib/Bcfg2/Server/BuiltinCore.py
+++ b/src/lib/Bcfg2/Server/BuiltinCore.py
@@ -34,7 +34,8 @@ class BuiltinCore(NetworkCore):
daemon_args = dict(uid=Bcfg2.Options.setup.daemon_uid,
gid=Bcfg2.Options.setup.daemon_gid,
umask=int(Bcfg2.Options.setup.umask, 8),
- detach_process=True)
+ detach_process=True,
+ files_preserve=self._logfilehandles())
if Bcfg2.Options.setup.daemon:
daemon_args['pidfile'] = TimeoutPIDLockFile(
Bcfg2.Options.setup.daemon, acquire_timeout=5)
@@ -44,6 +45,24 @@ class BuiltinCore(NetworkCore):
self.context = daemon.DaemonContext(**daemon_args)
__init__.__doc__ = NetworkCore.__init__.__doc__.split('.. -----')[0]
+ def _logfilehandles(self, logger=None):
+ """ Get a list of all filehandles logger, that have to be handled
+ with DaemonContext.files_preserve to keep looging working.
+
+ :param logger: The logger to get the file handles of. By default,
+ self.logger is used.
+ :type logger: logging.Logger
+ """
+ if logger is None:
+ logger = self.logger
+
+ handles = [handler.stream.fileno()
+ for handler in logger.handlers
+ if hasattr(handler, 'stream')]
+ if logger.parent:
+ handles += self._logfilehandles(logger.parent)
+ return handles
+
def _dispatch(self, method, args, dispatch_dict):
""" Dispatch XML-RPC method calls
diff --git a/src/lib/Bcfg2/Server/Cache.py b/src/lib/Bcfg2/Server/Cache.py
index d05eb0bf6..b3b906b2c 100644
--- a/src/lib/Bcfg2/Server/Cache.py
+++ b/src/lib/Bcfg2/Server/Cache.py
@@ -96,15 +96,19 @@ class _Cache(MutableMapping):
return len(list(iter(self)))
def expire(self, key=None):
- """ expire all items, or a specific item, from the cache """
+ """ expire all items, or a specific item, from the cache
+
+ :returns: number of expired entries
+ """
+
if key is None:
- expire(*self._tags)
+ return expire(*self._tags)
else:
tags = self._tags | set([key])
# py 2.5 doesn't support mixing *args and explicit keyword
# args
kwargs = dict(exact=True)
- expire(*tags, **kwargs)
+ return expire(*tags, **kwargs)
def __repr__(self):
return repr(dict(self))
@@ -152,7 +156,10 @@ def expire(*tags, **kwargs):
""" Expire all items, a set of items, or one specific item from
the cache. If ``exact`` is set to True, then if the given tag set
doesn't match exactly one item in the cache, nothing will be
- expired. """
+ expired.
+
+ :returns: number of expired entries
+ """
exact = kwargs.pop("exact", False)
count = 0
if not tags:
@@ -170,6 +177,8 @@ def expire(*tags, **kwargs):
for hook in _hooks:
hook(tags, exact, count)
+ return count
+
def add_expire_hook(func):
""" Add a hook that will be called when an item is expired from
diff --git a/src/lib/Bcfg2/Server/CherrypyCore.py b/src/lib/Bcfg2/Server/CherrypyCore.py
index 3cb0e291b..05c6c5a94 100644
--- a/src/lib/Bcfg2/Server/CherrypyCore.py
+++ b/src/lib/Bcfg2/Server/CherrypyCore.py
@@ -110,17 +110,21 @@ class CherrypyCore(NetworkCore):
return cherrypy.serving.response.body
def _daemonize(self):
- """ Drop privileges with
- :class:`cherrypy.process.plugins.DropPrivileges`, daemonize
- with :class:`cherrypy.process.plugins.Daemonizer`, and write a
+ """ Drop privileges, daemonize
+ with :class:`cherrypy.process.plugins.Daemonizer` and write a
PID file with :class:`cherrypy.process.plugins.PIDFile`. """
+ self._drop_privileges()
+ Daemonizer(cherrypy.engine).subscribe()
+ PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe()
+ return True
+
+ def _drop_privileges(self):
+ """ Drop privileges with
+ :class:`cherrypy.process.plugins.DropPrivileges` """
DropPrivileges(cherrypy.engine,
uid=Bcfg2.Options.setup.daemon_uid,
gid=Bcfg2.Options.setup.daemon_gid,
umask=int(Bcfg2.Options.setup.umask, 8)).subscribe()
- Daemonizer(cherrypy.engine).subscribe()
- PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe()
- return True
def _run(self):
""" Start the server listening. """
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 03ab40343..a1ee24e18 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -11,6 +11,7 @@ import threading
import time
import inspect
import lxml.etree
+import daemon
import Bcfg2.Server
import Bcfg2.Logger
import Bcfg2.Options
@@ -26,6 +27,7 @@ from Bcfg2.Server.Statistics import track_statistics
try:
from django.core.exceptions import ImproperlyConfigured
+ import django
import django.conf
HAS_DJANGO = True
except ImportError:
@@ -82,10 +84,14 @@ def close_db_connection(func):
""" The decorated function """
rv = func(self, *args, **kwargs)
if self._database_available: # pylint: disable=W0212
- from django import db
self.logger.debug("%s: Closing database connection" %
threading.current_thread().getName())
- db.close_connection()
+
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ for connection in django.db.connections.all():
+ connection.close()
+ else:
+ django.db.close_connection() # pylint: disable=E1101
return rv
return inner
@@ -113,7 +119,8 @@ class DefaultACL(Plugin, ClientACLs):
def check_acl_ip(self, address, rmi):
return (("." not in rmi and
not rmi.endswith("_debug") and
- rmi != 'get_statistics') or
+ rmi != 'get_statistics' and
+ rmi != 'expire_metadata_cache') or
address[0] == "127.0.0.1")
# in core we frequently want to catch all exceptions, regardless of
@@ -324,6 +331,10 @@ class Core(object):
select.select([famfd], [], [], 2)
elif not self.fam.pending():
terminate.wait(15)
+
+ if self.terminate.isSet():
+ break
+
if self.fam.pending():
try:
self._update_vcs_revision()
@@ -429,6 +440,7 @@ class Core(object):
self.logger.error("Unexpected instantiation failure for plugin %s"
% plugin, exc_info=1)
+ @close_db_connection
def shutdown(self):
""" Perform plugin and FAM shutdown tasks. """
if not self._running:
@@ -443,10 +455,6 @@ class Core(object):
for plugin in list(self.plugins.values()):
plugin.shutdown()
self.logger.info("%s: All plugins shut down" % self.name)
- if self._database_available:
- from django import db
- self.logger.info("%s: Closing database connection" % self.name)
- db.close_connection()
@property
def metadata_cache_mode(self):
@@ -681,7 +689,7 @@ class Core(object):
self.logger.debug("Building configuration for %s" % client)
start = time.time()
config = lxml.etree.Element("Configuration", version='2.0',
- revision=self.revision)
+ revision=str(self.revision))
try:
meta = self.build_metadata(client)
except MetadataConsistencyError:
@@ -1365,6 +1373,21 @@ class Core(object):
return "This method is deprecated and will be removed in a future " + \
"release\n%s" % self.fam.set_debug(debug)
+ @exposed
+ def expire_metadata_cache(self, _, hostnames=None):
+ """ Expire the metadata cache for one or all clients
+
+ :param hostnames: A list of hostnames to expire the metadata
+ cache for or None. If None the cache of
+ all clients will be expired.
+ :type hostnames: None or list of strings
+ """
+ if hostnames is not None:
+ for hostname in hostnames:
+ self.metadata_cache.expire(hostname)
+ else:
+ self.metadata_cache.expire()
+
class NetworkCore(Core):
""" A server core that actually listens on the network, can be
@@ -1424,9 +1447,9 @@ class NetworkCore(Core):
"\n.. automethod:: _daemonize\n"
def __str__(self):
- if hasattr(Bcfg2.Options.setup, "location"):
+ if hasattr(Bcfg2.Options.setup, "server"):
return "%s(%s)" % (self.__class__.__name__,
- Bcfg2.Options.setup.location)
+ Bcfg2.Options.setup.server)
else:
return Core.__str__(self)
@@ -1486,3 +1509,13 @@ class NetworkCore(Core):
""" Daemonize the server and write the pidfile. This must be
overridden by a core implementation. """
raise NotImplementedError
+
+ def _drop_privileges(self):
+ """ This is called if not daemonized and running as root to
+ drop the privileges to the configured daemon_uid and daemon_gid.
+ """
+ daemon.daemon.change_process_owner(
+ Bcfg2.Options.setup.daemon_uid,
+ Bcfg2.Options.setup.daemon_gid)
+ self.logger.debug("Dropped privileges to %s:%s." %
+ (os.getuid(), os.getgid()))
diff --git a/src/lib/Bcfg2/Server/Encryption.py b/src/lib/Bcfg2/Server/Encryption.py
index b60302871..c6cd4232e 100755
--- a/src/lib/Bcfg2/Server/Encryption.py
+++ b/src/lib/Bcfg2/Server/Encryption.py
@@ -176,7 +176,7 @@ def ssl_encrypt(plaintext, passwd, algorithm=None, salt=None):
def is_encrypted(val):
""" Make a best guess if the value is encrypted or not. This just
checks to see if ``val`` is a base64-encoded string whose content
- starts with "Salted__", so it may have (rare) false positives. It
+ starts with "Salted\\_\\_", so it may have (rare) false positives. It
will not have false negatives. """
try:
return b64decode(val).startswith("Salted__")
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
index c4b34a469..8f6e136fd 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -214,6 +214,7 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
def shutdown(self):
if self.started and self.notifier:
self.notifier.stop()
+ Pseudo.shutdown(self)
shutdown.__doc__ = Pseudo.shutdown.__doc__
def list_watches(self):
diff --git a/src/lib/Bcfg2/Server/Info.py b/src/lib/Bcfg2/Server/Info.py
index 6af561089..044dcdf0c 100644
--- a/src/lib/Bcfg2/Server/Info.py
+++ b/src/lib/Bcfg2/Server/Info.py
@@ -1,5 +1,5 @@
-""" Subcommands and helpers for bcfg2-info """
# -*- coding: utf-8 -*-
+""" Subcommands and helpers for bcfg2-info """
import os
import sys
@@ -12,6 +12,7 @@ import fnmatch
import argparse
import operator
import lxml.etree
+import traceback
from code import InteractiveConsole
import Bcfg2.Logger
import Bcfg2.Options
@@ -142,9 +143,7 @@ class Debug(InfoCmd):
if setup.cmd_list:
console = InteractiveConsole(locals())
for command in setup.cmd_list.readlines():
- command = command.strip()
- if command:
- console.push(command)
+ console.push(command.rstrip())
if not setup.non_interactive:
print("Dropping to interpreter; press ^D to resume")
self.interpreters[setup.interpreter](self.core.get_locals())
@@ -369,6 +368,7 @@ class Automatch(InfoCmd):
class ExpireCache(InfoCmd):
""" Expire the metadata cache """
+ only_interactive = True
options = [
Bcfg2.Options.PositionalArgument(
@@ -376,12 +376,20 @@ class ExpireCache(InfoCmd):
help="Expire cache for the given host(s)")]
def run(self, setup):
- if setup.clients:
- for client in self.get_client_list(setup.clients):
- self.core.expire_caches_by_type(Bcfg2.Server.Plugin.Metadata,
- key=client)
+ if setup.hostname:
+ for client in self.get_client_list(setup.hostname):
+ self.core.metadata_cache.expire(client)
else:
- self.core.expire_caches_by_type(Bcfg2.Server.Plugin.Metadata)
+ self.core.metadata_cache.expire()
+
+
+class EventDebug(InfoCmd):
+ """ Enable debugging output for FAM events """
+ only_interactive = True
+ aliases = ['event_debug']
+
+ def run(self, _):
+ self.core.fam.set_debug(True)
class Bundles(InfoCmd):
@@ -672,18 +680,35 @@ class Query(InfoCmd):
print("\n".join(res))
+class Quit(InfoCmd):
+ """ Exit program """
+ only_interactive = True
+ aliases = ['exit', 'EOF']
+
+ def run(self, _):
+ raise SystemExit(0)
+
+
class Shell(InfoCmd):
""" Open an interactive shell to run multiple bcfg2-info commands """
interactive = False
def run(self, setup):
try:
- self.core.cmdloop('Welcome to bcfg2-info\n'
- 'Type "help" for more information')
+ self.core.cli.cmdloop('Welcome to bcfg2-info\n'
+ 'Type "help" for more information')
except KeyboardInterrupt:
print("\nCtrl-C pressed, exiting...")
+class Update(InfoCmd):
+ """ Process pending filesystem events """
+ only_interactive = True
+
+ def run(self, _):
+ self.core.fam.handle_events_in_interval(0.1)
+
+
class ProfileTemplates(InfoCmd):
""" Benchmark template rendering times """
@@ -796,36 +821,18 @@ if HAS_PROFILE:
display_trace(prof)
-class InfoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
+class InfoCore(Bcfg2.Server.Core.Core):
"""Main class for bcfg2-info."""
- def __init__(self):
- cmd.Cmd.__init__(self)
+ def __init__(self, cli):
Bcfg2.Server.Core.Core.__init__(self)
- self.prompt = 'bcfg2-info> '
+ self.cli = cli
def get_locals(self):
""" Expose the local variables of the core to subcommands that
need to reference them (i.e., the interactive interpreter) """
return locals()
- def do_quit(self, _):
- """ quit|exit - Exit program """
- raise SystemExit(0)
-
- do_EOF = do_quit
- do_exit = do_quit
-
- def do_eventdebug(self, _):
- """ eventdebug - Enable debugging output for FAM events """
- self.fam.set_debug(True)
-
- do_event_debug = do_eventdebug
-
- def do_update(self, _):
- """ update - Process pending filesystem events """
- self.fam.handle_events_in_interval(0.1)
-
def run(self):
self.load_plugins()
self.block_for_fam_events(handle_events=True)
@@ -840,12 +847,15 @@ class InfoCore(cmd.Cmd, Bcfg2.Server.Core.Core):
Bcfg2.Server.Core.Core.shutdown(self)
-class CLI(Bcfg2.Options.CommandRegistry):
+class CLI(cmd.Cmd, Bcfg2.Options.CommandRegistry):
""" The bcfg2-info CLI """
options = [Bcfg2.Options.BooleanOption("-p", "--profile", help="Profile")]
def __init__(self):
+ cmd.Cmd.__init__(self)
Bcfg2.Options.CommandRegistry.__init__(self)
+ self.prompt = 'bcfg2-info> '
+
self.register_commands(globals().values(), parent=InfoCmd)
parser = Bcfg2.Options.get_parser(
description="Inspect a running Bcfg2 server",
@@ -860,7 +870,7 @@ class CLI(Bcfg2.Options.CommandRegistry):
else:
if Bcfg2.Options.setup.profile:
print("Profiling functionality not available.")
- self.core = InfoCore()
+ self.core = InfoCore(self)
for command in self.commands.values():
command.core = self.core
@@ -877,3 +887,22 @@ class CLI(Bcfg2.Options.CommandRegistry):
def shutdown(self):
Bcfg2.Options.CommandRegistry.shutdown(self)
self.core.shutdown()
+
+ def get_names(self):
+ """ Overwrite cmd.Cmd.get_names to use the instance to get the
+ methods and not the class, because the CommandRegistry
+ dynamically adds methods for the registed subcommands. """
+ return dir(self)
+
+ def onecmd(self, line):
+ """ Overwrite cmd.Cmd.onecmd to catch all exceptions (except
+ SystemExit) print an error message and continue cmdloop. """
+ try:
+ cmd.Cmd.onecmd(self, line)
+ except SystemExit:
+ raise
+ except: # pylint: disable=W0702
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ lines = traceback.format_exception(exc_type, exc_value,
+ exc_traceback)
+ self.stdout.write(''.join(lines))
diff --git a/src/lib/Bcfg2/Server/Lint/Bundler.py b/src/lib/Bcfg2/Server/Lint/Bundler.py
index 576e157ad..7b024229b 100644
--- a/src/lib/Bcfg2/Server/Lint/Bundler.py
+++ b/src/lib/Bcfg2/Server/Lint/Bundler.py
@@ -29,8 +29,12 @@ class Bundler(ServerPlugin):
# when given a list of files on stdin, this check is
# useless, so skip it
groupdata = self.metadata.groups_xml.xdata
+
ref_bundles = set([b.get("name")
for b in groupdata.findall("//Bundle")])
+ for bundle in self.core.plugins['Bundler'].bundles.values():
+ ref_bundles |= set([rb.get("name") for rb in
+ bundle.xdata.findall(".//RequiredBundle")])
allbundles = self.core.plugins['Bundler'].bundles.keys()
for bundle in ref_bundles:
diff --git a/src/lib/Bcfg2/Server/Lint/MergeFiles.py b/src/lib/Bcfg2/Server/Lint/MergeFiles.py
index 8e6a926ae..3a6251594 100644
--- a/src/lib/Bcfg2/Server/Lint/MergeFiles.py
+++ b/src/lib/Bcfg2/Server/Lint/MergeFiles.py
@@ -6,6 +6,7 @@ import copy
from difflib import SequenceMatcher
import Bcfg2.Server.Lint
from Bcfg2.Server.Plugins.Cfg import CfgGenerator
+from Bcfg2.Utils import is_string
def threshold(val):
@@ -50,6 +51,8 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
for filename, entryset in self.core.plugins['Cfg'].entries.items():
candidates = dict([(f, e) for f, e in entryset.entries.items()
if (isinstance(e, CfgGenerator) and
+ is_string(e.data,
+ Bcfg2.Options.setup.encoding) and
f not in ignore and
not f.endswith(".crypt"))])
similar, identical = self.get_similar(candidates)
diff --git a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
index ebf4c4954..56b4e7477 100644
--- a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
+++ b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
@@ -47,70 +47,118 @@ def is_device_mode(val):
return re.match(r'^\d+$', val)
+def is_vcs_type(val):
+ """ Return True if val is a supported vcs type handled by the
+ current client tool """
+ return (val != 'Path' and
+ hasattr(Bcfg2.Client.Tools.VCS.VCS, 'Install%s' % val))
+
+
class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
""" Verify attributes for configuration entries that cannot be
verified with an XML schema alone. """
def __init__(self, *args, **kwargs):
Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs)
- self.required_attrs = dict(
- Path=dict(
- device=dict(name=is_filename,
- owner=is_username,
- group=is_username,
- dev_type=lambda v: v in device_map),
- directory=dict(name=is_filename, owner=is_username,
- group=is_username, mode=is_octal_mode),
- file=dict(name=is_filename, owner=is_username,
- group=is_username, mode=is_octal_mode,
- __text__=None),
- hardlink=dict(name=is_filename, to=is_filename),
- symlink=dict(name=is_filename),
- ignore=dict(name=is_filename),
- nonexistent=dict(name=is_filename),
- permissions=dict(name=is_filename, owner=is_username,
- group=is_username, mode=is_octal_mode),
- vcs=dict(vcstype=lambda v: (v != 'Path' and
- hasattr(Bcfg2.Client.Tools.VCS.VCS,
- "Install%s" % v)),
- revision=None, sourceurl=None)),
- Service={"__any__": dict(name=None),
- "smf": dict(name=None, FMRI=None)},
- Action={None: dict(name=None,
- timing=lambda v: v in ['pre', 'post', 'both'],
- when=lambda v: v in ['modified', 'always'],
- status=lambda v: v in ['ignore', 'check'],
- command=None)},
- ACL=dict(
- default=dict(scope=lambda v: v in ['user', 'group'],
- perms=lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}',
- v)),
- access=dict(scope=lambda v: v in ['user', 'group'],
- perms=lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}',
- v)),
- mask=dict(perms=lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}',
- v))),
- Package={"__any__": dict(name=None)},
- SEBoolean={None: dict(name=None,
- value=lambda v: v in ['on', 'off'])},
- SEModule={None: dict(name=None, __text__=None)},
- SEPort={
- None: dict(name=lambda v: re.match(r'^\d+(-\d+)?/(tcp|udp)',
- v),
- selinuxtype=is_selinux_type)},
- SEFcontext={None: dict(name=None, selinuxtype=is_selinux_type)},
- SENode={None: dict(name=lambda v: "/" in v,
- selinuxtype=is_selinux_type,
- proto=lambda v: v in ['ipv6', 'ipv4'])},
- SELogin={None: dict(name=is_username,
- selinuxuser=is_selinux_user)},
- SEUser={None: dict(name=is_selinux_user,
- roles=lambda v: all(is_selinux_user(u)
- for u in " ".split(v)),
- prefix=None)},
- SEInterface={None: dict(name=None, selinuxtype=is_selinux_type)},
- SEPermissive={None: dict(name=is_selinux_type)},
- POSIXGroup={None: dict(name=is_username)},
- POSIXUser={None: dict(name=is_username)})
+ self.required_attrs = {
+ 'Path': {
+ '__any__': {'name': is_filename},
+ 'augeas': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode},
+ 'device': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode,
+ 'dev_type': lambda v: v in device_map},
+ 'directory': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode},
+ 'file': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode, '__text__': None},
+ 'hardlink': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode, 'to': is_filename},
+ 'symlink': {},
+ 'ignore': {},
+ 'nonexistent': {},
+ 'permissions': {'owner': is_username, 'group': is_username,
+ 'mode': is_octal_mode},
+ 'vcs': {'vcstype': is_vcs_type, 'revision': None,
+ 'sourceurl': None},
+ },
+ 'Service': {
+ '__any__': {'name': None},
+ 'smf': {'name': None, 'FMRI': None}
+ },
+ 'Action': {
+ None: {
+ 'name': None,
+ 'timing': lambda v: v in ['pre', 'post', 'both'],
+ 'when': lambda v: v in ['modified', 'always'],
+ 'status': lambda v: v in ['ignore', 'check'],
+ 'command': None,
+ },
+ },
+ 'ACL': {
+ 'default': {
+ 'scope': lambda v: v in ['user', 'group'],
+ 'perms': lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}', v),
+ },
+ 'access': {
+ 'scope': lambda v: v in ['user', 'group'],
+ 'perms': lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}', v),
+ },
+ 'mask': {
+ 'perms': lambda v: re.match(r'^([0-7]|[rwx\-]{0,3}', v),
+ },
+ },
+ 'Package': {
+ '__any__': {'name': None},
+ },
+ 'SEBoolean': {
+ None: {
+ 'name': None,
+ 'value': lambda v: v in ['on', 'off'],
+ },
+ },
+ 'SEModule': {
+ None: {'name': None, '__text__': None},
+ },
+ 'SEPort': {
+ None: {
+ 'name': lambda v: re.match(r'^\d+(-\d+)?/(tcp|udp)', v),
+ 'selinuxtype': is_selinux_type,
+ },
+ },
+ 'SEFcontext': {
+ None: {'name': None, 'selinuxtype': is_selinux_type},
+ },
+ 'SENode': {
+ None: {
+ 'name': lambda v: "/" in v,
+ 'selinuxtype': is_selinux_type,
+ 'proto': lambda v: v in ['ipv6', 'ipv4']
+ },
+ },
+ 'SELogin': {
+ None: {'name': is_username, 'selinuxuser': is_selinux_user},
+ },
+ 'SEUser': {
+ None: {
+ 'name': is_selinux_user,
+ 'roles': lambda v: all(is_selinux_user(u)
+ for u in " ".split(v)),
+ 'prefix': None,
+ },
+ },
+ 'SEInterface': {
+ None: {'name': None, 'selinuxtype': is_selinux_type},
+ },
+ 'SEPermissive': {
+ None: {'name': is_selinux_type},
+ },
+ 'POSIXGroup': {
+ None: {'name': is_username},
+ },
+ 'POSIXUser': {
+ None: {'name': is_username},
+ },
+ }
def Run(self):
self.check_packages()
diff --git a/src/lib/Bcfg2/Server/Lint/TemplateHelper.py b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py
index 9d05516f1..98faa269d 100644
--- a/src/lib/Bcfg2/Server/Lint/TemplateHelper.py
+++ b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py
@@ -4,8 +4,8 @@
import sys
import imp
from Bcfg2.Server.Lint import ServerPlugin
-from Bcfg2.Server.Plugins.TemplateHelper import HelperModule, MODULE_RE, \
- safe_module_name
+from Bcfg2.Server.Plugins.TemplateHelper import HelperModule, MODULE_RE
+from Bcfg2.Utils import safe_module_name
class TemplateHelper(ServerPlugin):
@@ -26,7 +26,7 @@ class TemplateHelper(ServerPlugin):
ServerPlugin.__init__(self, *args, **kwargs)
# we instantiate a dummy helper to discover which keywords and
# defaults are reserved
- dummy = HelperModule("foo.py")
+ dummy = HelperModule("foo.py", None)
self.reserved_keywords = dir(dummy)
self.reserved_defaults = dummy.reserved_defaults
@@ -44,7 +44,8 @@ class TemplateHelper(ServerPlugin):
module_name = MODULE_RE.search(helper).group(1)
try:
- module = imp.load_source(safe_module_name(module_name), helper)
+ module = imp.load_source(
+ safe_module_name('TemplateHelper', module_name), helper)
except: # pylint: disable=W0702
err = sys.exc_info()[1]
self.LintError("templatehelper-import-error",
diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py
index d6f18afbb..146f18b0c 100644
--- a/src/lib/Bcfg2/Server/Lint/Validate.py
+++ b/src/lib/Bcfg2/Server/Lint/Validate.py
@@ -61,7 +61,8 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"AWSTags/config.xml": "awstags.xsd",
"NagiosGen/config.xml": "nagiosgen.xsd",
"FileProbes/config.xml": "fileprobes.xsd",
- "GroupLogic/groups.xml": "grouplogic.xsd"
+ "GroupLogic/groups.xml": "grouplogic.xsd",
+ "PkgVars/*.xml": "pkgvars.xsd"
}
self.filelists = {}
diff --git a/src/lib/Bcfg2/Server/Lint/__init__.py b/src/lib/Bcfg2/Server/Lint/__init__.py
index 61f704206..873e5f149 100644
--- a/src/lib/Bcfg2/Server/Lint/__init__.py
+++ b/src/lib/Bcfg2/Server/Lint/__init__.py
@@ -14,6 +14,7 @@ import time
import lxml.etree
+
import Bcfg2.Options
import Bcfg2.Server.Core
import Bcfg2.Server.Plugins
diff --git a/src/lib/Bcfg2/Server/MultiprocessingCore.py b/src/lib/Bcfg2/Server/MultiprocessingCore.py
index 724b34d8d..4bf3e4a27 100644
--- a/src/lib/Bcfg2/Server/MultiprocessingCore.py
+++ b/src/lib/Bcfg2/Server/MultiprocessingCore.py
@@ -334,9 +334,9 @@ class MultiprocessingCore(BuiltinCore):
self.children = None
def __str__(self):
- if hasattr(Bcfg2.Options.setup, "location"):
+ if hasattr(Bcfg2.Options.setup, "server"):
return "%s(%s; %s children)" % (self.__class__.__name__,
- Bcfg2.Options.setup.location,
+ Bcfg2.Options.setup.server,
len(self._all_children))
else:
return "%s(%s children)" % (self.__class__.__name__,
diff --git a/src/lib/Bcfg2/Server/Plugin/__init__.py b/src/lib/Bcfg2/Server/Plugin/__init__.py
index e28e458b3..69fc90b2f 100644
--- a/src/lib/Bcfg2/Server/Plugin/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugin/__init__.py
@@ -11,7 +11,6 @@ documentation it's not necessary to use the submodules. E.g., you can
from Bcfg2.Server.Plugin.base import Plugin
"""
-
import Bcfg2.Options
# pylint: disable=W0401
diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py
index 245cfc256..ca0fe8188 100644
--- a/src/lib/Bcfg2/Server/Plugin/helpers.py
+++ b/src/lib/Bcfg2/Server/Plugin/helpers.py
@@ -13,7 +13,7 @@ import Bcfg2.Server
import Bcfg2.Options
import Bcfg2.Server.FileMonitor
from Bcfg2.Logger import Debuggable
-from Bcfg2.Compat import CmpMixin, wraps
+from Bcfg2.Compat import CmpMixin, MutableMapping, wraps
from Bcfg2.Server.Plugin.base import Plugin
from Bcfg2.Server.Plugin.interfaces import Generator, TemplateDataProvider
from Bcfg2.Server.Plugin.exceptions import SpecificityError, \
@@ -1066,7 +1066,22 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
data = candidate
break
- entry.text = data.text
+ self._apply(entry, data)
+
+ def _apply(self, entry, data):
+ """ Apply all available values from data onto entry. This
+ sets the available attributes (for all attribues unset in
+ the entry), adds all children and copies the text from data
+ to entry.
+
+ :param entry: The entry to apply the changes
+ :type entry: lxml.etree._Element
+ :param data: The entry to get the data from
+ :type data: lxml.etree._Element
+ """
+
+ if data.text is not None and data.text.strip() != '':
+ entry.text = data.text
for item in data.getchildren():
entry.append(copy.copy(item))
@@ -1685,3 +1700,84 @@ class GroupSpool(Plugin, Generator):
return
reqid = self.fam.AddMonitor(name, self)
self.handles[reqid] = relative
+
+
+class CallableDict(MutableMapping):
+ """ This maps a set of keys to a set of value-getting functions;
+ the values are populated on-the-fly by the functions as the values
+ are needed (and not before). This is for example used by
+ :func:`Bcfg2.Server.Plugins.Packages.Packages.get_additional_data`;
+ see the docstring for that function for details on why.
+
+ Unlike a dict, you can specify values or functions for the
+ righthand side of this mapping. If you specify a function, it will
+ be evaluated everytime you access the value. E.g.:
+
+ .. code-block:: python
+
+ d = CallableDict(foo=load_foo,
+ bar="bar")
+ """
+
+ def __init__(self, **getters):
+ self._getters = dict(**getters)
+
+ def __getitem__(self, key):
+ if callable(self._getters[key]):
+ return self._getters[key]()
+ else:
+ return self._getters[key]
+
+ def __setitem__(self, key, getter):
+ self._getters[key] = getter
+
+ def __delitem__(self, key):
+ del self._getters[key]
+
+ def __len__(self):
+ return len(self._getters)
+
+ def __iter__(self):
+ return iter(self._getters.keys())
+
+ def _current_data(self):
+ """ Return a dict with the current available static data
+ and ``unknown`` for all callable values.
+ """
+ rv = dict()
+ for key in self._getters.keys():
+ if callable(self._getters[key]):
+ rv[key] = 'unknown'
+ else:
+ rv[key] = self._getters[key]
+ return rv
+
+ def __repr__(self):
+ return str(self._current_data())
+
+
+class OnDemandDict(CallableDict):
+ """ This is like a :class:`CallableDict` but it will cache
+ the results of the callable getters, so that it is only evaluated
+ once when you first access it.
+ """
+
+ def __init__(self, **getters):
+ CallableDict.__init__(self, **getters)
+ self._values = dict()
+
+ def __getitem__(self, key):
+ if key not in self._values:
+ self._values[key] = super(OnDemandDict, self).__getitem__(key)
+ return self._values[key]
+
+ def __delitem__(self, key):
+ super(OnDemandDict, self).__delitem__(key)
+ del self._values[key]
+
+ def _current_data(self):
+ rv = super(OnDemandDict, self)._current_data()
+ for (key, value) in rv.items():
+ if key in self._values:
+ rv[key] = value
+ return rv
diff --git a/src/lib/Bcfg2/Server/Plugins/AWSTags.py b/src/lib/Bcfg2/Server/Plugins/AWSTags.py
index 0d6eefaaa..556805bde 100644
--- a/src/lib/Bcfg2/Server/Plugins/AWSTags.py
+++ b/src/lib/Bcfg2/Server/Plugins/AWSTags.py
@@ -172,6 +172,11 @@ class AWSTags(Bcfg2.Server.Plugin.Plugin,
def start_client_run(self, metadata):
self.expire_cache(key=metadata.hostname)
+ if self.core.metadata_cache_mode == 'aggressive':
+ self.logger.warning("AWSTags is incompatible with aggressive "
+ "client metadata caching, try 'cautious' "
+ "or 'initial'")
+ self.core.metadata_cache.expire(metadata.hostname)
def get_additional_data(self, metadata):
return self.get_tags(metadata)
diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py
index e38eeea89..4f5a79465 100644
--- a/src/lib/Bcfg2/Server/Plugins/Bundler.py
+++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py
@@ -92,6 +92,10 @@ class Bundler(Plugin,
self.logger.error("Bundler: Failed to render templated bundle "
"%s: %s" % (bundlename, err))
continue
+ except:
+ self.logger.error("Bundler: Unexpected bundler error for %s" %
+ bundlename, exc_info=1)
+ continue
if data.get("independent", "false").lower() == "true":
data.tag = "Independent"
@@ -124,12 +128,12 @@ class Bundler(Plugin,
# dependent bundle -- add it to the list of
# bundles for this client
if child.get("name") not in bundles_added:
- bundles.append(child.get("name"))
+ bundles.add(child.get("name"))
bundles_added.add(child.get("name"))
if child.get('inherit_modification', 'false') == 'true':
if metadata.version_info >= \
Bcfg2VersionInfo('1.4.0pre2'):
- lxml.etree.SubElement(data, 'Bundle',
+ lxml.etree.SubElement(data, 'BoundBundle',
name=child.get('name'))
else:
self.logger.warning(
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py
index cff9ff61e..71aec7658 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py
@@ -12,14 +12,14 @@ from Bcfg2.Server.Plugins.Cfg import CfgGenerator
try:
from jinja2 import Environment, FileSystemLoader
HAS_JINJA2 = True
-except ImportError:
- HAS_JINJA2 = False
+ class RelEnvironment(Environment):
+ """Override join_path() to enable relative template paths."""
+ def join_path(self, template, parent):
+ return os.path.join(os.path.dirname(parent), template)
-class RelEnvironment(Environment):
- """Override join_path() to enable relative template paths."""
- def join_path(self, template, parent):
- return os.path.join(os.path.dirname(parent), template)
+except ImportError:
+ HAS_JINJA2 = False
class DefaultJinja2DataProvider(DefaultTemplateDataProvider):
@@ -42,15 +42,16 @@ class CfgJinja2Generator(CfgGenerator):
#: Handle .jinja2 files
__extensions__ = ['jinja2']
- #: ``__loader_cls__`` is the class that will be instantiated to
- #: load the template files. It must implement one public function,
- #: ``load()``, as :class:`genshi.template.TemplateLoader`.
- __loader_cls__ = FileSystemLoader
+ if HAS_JINJA2:
+ #: ``__loader_cls__`` is the class that will be instantiated to
+ #: load the template files. It must implement one public function,
+ #: ``load()``, as :class:`genshi.template.TemplateLoader`.
+ __loader_cls__ = FileSystemLoader
- #: ``__environment_cls__`` is the class that will be instantiated to
- #: store the jinja2 environment. It must implement one public function,
- #: ``get_template()``, as :class:`jinja2.Environment`.
- __environment_cls__ = RelEnvironment
+ #: ``__environment_cls__`` is the class that will be instantiated to
+ #: store the jinja2 environment. It must implement one public
+ #: function, ``get_template()``, as :class:`jinja2.Environment`.
+ __environment_cls__ = RelEnvironment
#: Ignore ``.jinja2_include`` files so they can be used with the
#: Jinja2 ``{% include ... %}`` directive without raising warnings.
@@ -68,7 +69,15 @@ class CfgJinja2Generator(CfgGenerator):
encoding = Bcfg2.Options.setup.encoding
self.loader = self.__loader_cls__('/',
encoding=encoding)
- self.environment = self.__environment_cls__(loader=self.loader)
+ try:
+ # keep_trailing_newline is new in Jinja2 2.7, and will
+ # fail with earlier versions
+ self.environment = \
+ self.__environment_cls__(loader=self.loader,
+ keep_trailing_newline=True)
+ except TypeError:
+ self.environment = \
+ self.__environment_cls__(loader=self.loader)
__init__.__doc__ = CfgGenerator.__init__.__doc__
def get_data(self, entry, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py
index a158302be..241bce34c 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py
@@ -19,8 +19,8 @@ class CfgSSLCAKeyCreator(XMLCfgCreator):
self.logger.info("Cfg: Generating new SSL key for %s" % self.name)
spec = self.XMLMatch(metadata)
key = spec.find("Key")
- if not key:
- key = dict()
+ if key is None:
+ key = {}
ktype = key.get('type', 'rsa')
bits = key.get('bits', '2048')
if ktype == 'rsa':
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index 355e53588..dae03144a 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -878,6 +878,7 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool,
options = Bcfg2.Server.Plugin.GroupSpool.options + [
Bcfg2.Options.BooleanOption(
'--cfg-validation', cf=('cfg', 'validation'), default=True,
+ dest="cfg_validation",
help='Run validation on Cfg files'),
Bcfg2.Options.Option(
cf=('cfg', 'category'), dest="cfg_category",
diff --git a/src/lib/Bcfg2/Server/Plugins/Defaults.py b/src/lib/Bcfg2/Server/Plugins/Defaults.py
index 79e2ca0e2..2242e3825 100644
--- a/src/lib/Bcfg2/Server/Plugins/Defaults.py
+++ b/src/lib/Bcfg2/Server/Plugins/Defaults.py
@@ -1,5 +1,6 @@
"""This generator provides rule-based entry mappings."""
+import Bcfg2.Options
import Bcfg2.Server.Plugin
import Bcfg2.Server.Plugins.Rules
@@ -9,7 +10,10 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules,
"""Set default attributes on bound entries"""
__author__ = 'bcfg-dev@mcs.anl.gov'
- options = Bcfg2.Server.Plugin.PrioDir.options
+ options = Bcfg2.Server.Plugin.PrioDir.options + [
+ Bcfg2.Options.BooleanOption(
+ cf=("defaults", "replace_name"), dest="defaults_replace_name",
+ help="Replace %{name} in attributes with name of target entry")]
# Rules is a Generator that happens to implement all of the
# functionality we want, so we overload it, but Defaults should
@@ -41,3 +45,9 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules,
def _regex_enabled(self):
""" Defaults depends on regex matching, so force it enabled """
return True
+
+ @property
+ def _replace_name_enabled(self):
+ """ Return True if the replace_name feature is enabled,
+ False otherwise """
+ return Bcfg2.Options.setup.defaults_replace_name
diff --git a/src/lib/Bcfg2/Server/Plugins/GroupLogic.py b/src/lib/Bcfg2/Server/Plugins/GroupLogic.py
index b60f60e65..184b362f9 100644
--- a/src/lib/Bcfg2/Server/Plugins/GroupLogic.py
+++ b/src/lib/Bcfg2/Server/Plugins/GroupLogic.py
@@ -13,6 +13,17 @@ class GroupLogicConfig(Bcfg2.Server.Plugin.StructFile):
create = lxml.etree.Element("GroupLogic",
nsmap=dict(py="http://genshi.edgewall.org/"))
+ def __init__(self, filename, core):
+ Bcfg2.Server.Plugin.StructFile.__init__(self, filename,
+ should_monitor=True)
+ self.core = core
+
+ def Index(self):
+ Bcfg2.Server.Plugin.StructFile.Index(self)
+
+ if self.core.metadata_cache_mode in ['cautious', 'aggressive']:
+ self.core.metadata_cache.expire()
+
def _match(self, item, metadata, *args):
if item.tag == 'Group' and not len(item.getchildren()):
return [item]
@@ -39,7 +50,7 @@ class GroupLogic(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.Plugin.__init__(self, core)
Bcfg2.Server.Plugin.Connector.__init__(self)
self.config = GroupLogicConfig(os.path.join(self.data, "groups.xml"),
- should_monitor=True)
+ core=core)
self._local = local()
def get_additional_groups(self, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/Ldap.py b/src/lib/Bcfg2/Server/Plugins/Ldap.py
index 553ddbc47..770419ba5 100644
--- a/src/lib/Bcfg2/Server/Plugins/Ldap.py
+++ b/src/lib/Bcfg2/Server/Plugins/Ldap.py
@@ -1,169 +1,264 @@
+""" A plugin to fetch data from a LDAP directory """
+
import imp
-import logging
+import os
import sys
import time
import traceback
-import Bcfg2.Server.Plugin
+from functools import partial
-logger = logging.getLogger('Bcfg2.Plugins.Ldap')
+import Bcfg2.Options
+import Bcfg2.Server.Cache
+import Bcfg2.Server.Plugin
+from Bcfg2.Logger import Debuggable
+from Bcfg2.Utils import ClassName, safe_module_name
try:
import ldap
+ HAS_LDAP = True
except ImportError:
- logger.error("Unable to load ldap module. Is python-ldap installed?")
- raise ImportError
+ HAS_LDAP = False
-# time in seconds between retries after failed LDAP connection
-RETRY_DELAY = 5
-# how many times to try reaching the LDAP server if a connection is broken
-# at the very minimum, one retry is needed to handle a restarted LDAP daemon
-RETRY_COUNT = 3
-SCOPE_MAP = {
- "base": ldap.SCOPE_BASE,
- "one": ldap.SCOPE_ONELEVEL,
- "sub": ldap.SCOPE_SUBTREE,
-}
+class ConfigFile(Bcfg2.Server.Plugin.FileBacked):
+ """ Config file for the Ldap plugin """
-LDAP_QUERIES = []
+ def __init__(self, name, core, plugin):
+ Bcfg2.Server.Plugin.FileBacked.__init__(self, name)
+ self.core = core
+ self.plugin = plugin
+ self.queries = list()
+ self.fam.AddMonitor(name, self)
+ def Index(self):
+ """ Get the queries from the config file """
+ try:
+ module_name = os.path.splitext(os.path.basename(self.name))[0]
+ module = imp.load_source(safe_module_name('Ldap', module_name),
+ self.name)
+ except: # pylint: disable=W0702
+ err = sys.exc_info()[1]
+ self.logger.error("Ldap: Failed to import %s: %s" %
+ (self.name, err))
+ return
+
+ if not hasattr(module, "__queries__"):
+ self.logger.error("Ldap: %s has no __queries__ list" % self.name)
+ return
+
+ self.queries = list()
+ for query in module.__queries__:
+ try:
+ self.queries.append(getattr(module, query))
+ except AttributeError:
+ self.logger.warning(
+ "Ldap: %s exports %s, but has no such attribute" %
+ (self.name, query))
-def register_query(query):
- LDAP_QUERIES.append(query)
+ if self.core.metadata_cache_mode in ['cautious', 'aggressive']:
+ self.core.metadata_cache.expire()
+ self.plugin.expire_cache()
-class ConfigFile(Bcfg2.Server.Plugin.FileBacked):
- """
- Config file for the Ldap plugin
-
- The config file cannot be 'parsed' in the traditional sense as we would
- need some serious type checking ugliness to just get the LdapQuery
- subclasses. The alternative would be to have the user create a list with
- a predefined name that contains all queries.
- The approach implemented here is having the user call a registering
- decorator that updates a global variable in this module.
- """
- def __init__(self, filename):
- self.filename = filename
- Bcfg2.Server.Plugin.FileBacked.__init__(self, self.filename)
- self.fam.AddMonitor(self.filename, self)
-
- 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.
- """
- global LDAP_QUERIES
- LDAP_QUERIES = []
- imp.load_source("ldap_cfg", self.filename)
+class Ldap(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.ClientRunHooks,
+ Bcfg2.Server.Plugin.Connector):
+ """ The Ldap plugin allows adding data from an LDAP server
+ to your metadata. """
+ __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['expire_cache']
-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"
experimental = True
- debug_flag = False
+
+ options = [
+ Bcfg2.Options.Option(
+ cf=('ldap', 'retries'), type=int, default=3,
+ dest='ldap_retries',
+ help='The number of times to retry reaching the '
+ 'LDAP server if a connection is broken'),
+ Bcfg2.Options.Option(
+ cf=('ldap', 'retry_delay'), type=float, default=5.0,
+ dest='ldap_retry_delay',
+ help='The time in seconds betreen retries'),
+ Bcfg2.Options.BooleanOption(
+ cf=('ldap', 'cache'), default=None, dest='ldap_cache',
+ help='Cache the results of the LDAP Queries until they '
+ 'are expired using the XML-RPC RMI')]
def __init__(self, core):
Bcfg2.Server.Plugin.Plugin.__init__(self, core)
Bcfg2.Server.Plugin.Connector.__init__(self)
- self.config = ConfigFile(self.data + "/config.py")
- def debug_log(self, message, flag=None):
- if (flag is None) and self.debug_flag or flag:
- self.logger.error(message)
+ if not HAS_LDAP:
+ msg = "Python ldap module is required for Ldap plugin"
+ self.logger.error(msg)
+ raise Bcfg2.Server.Plugin.PluginInitError(msg)
+
+ self.config = ConfigFile(os.path.join(self.data, 'config.py'),
+ core, self)
+ self._hosts = dict()
+
+ def _cache(self, query_name):
+ """ Return the :class:`Cache <Bcfg2.Server.Cache>` for the
+ given query name. """
+ return Bcfg2.Server.Cache.Cache('Ldap', 'results', query_name)
+
+ def _execute_query(self, query, metadata):
+ """ Return the cached result of the given query for this host or
+ execute the given query and cache the result. """
+ result = None
+
+ if Bcfg2.Options.setup.ldap_cache is not False:
+ cache = self._cache(query.name)
+ result = cache.get(metadata.hostname, None)
+
+ if result is None:
+ try:
+ self.debug_log("Processing query '%s'" % query.name)
+ result = query.get_result(metadata)
+ if Bcfg2.Options.setup.ldap_cache is not False:
+ cache[metadata.hostname] = result
+ except: # pylint: disable=W0702
+ self.logger.error(
+ "Exception during processing of query named '%s', query "
+ "results will be empty and may cause bind failures" %
+ query.name)
+ for line in traceback.format_exc().split('\n'):
+ self.logger.error(line)
+ return result
def get_additional_data(self, metadata):
- query = None
- try:
- data = {}
- self.debug_log("LdapPlugin debug: found queries " +
- str(LDAP_QUERIES))
- for QueryClass in LDAP_QUERIES:
- query = QueryClass()
+ data = {}
+ self.debug_log("Found queries %s" % self.config.queries)
+ for query_class in self.config.queries:
+ try:
+ query = query_class()
if query.is_applicable(metadata):
- self.debug_log("LdapPlugin debug: processing query '" +
- query.name + "'")
- data[query.name] = query.get_result(metadata)
+ self.debug_log("Processing query '%s'" % query.name)
+ data[query.name] = partial(
+ self._execute_query, query, metadata)
else:
- self.debug_log("LdapPlugin debug: query '" + query.name +
- "' not applicable to host '" +
- metadata.hostname + "'")
- return data
- except Exception:
- if hasattr(query, "name"):
- logger.error("LdapPlugin error: " +
- "Exception during processing of query named '" +
- str(query.name) +
- "', 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]):
- logger.error("LdapPlugin error: " +
- line.replace("\n", ""))
- return {}
-
-
-class LdapConnection(object):
- """
- Connection to an LDAP server.
- """
- def __init__(self, host="localhost", port=389,
+ self.debug_log("query '%s' not applicable to host '%s'" %
+ (query.name, metadata.hostname))
+ except: # pylint: disable=W0702
+ self.logger.error(
+ "Exception during preparation of query named '%s'. "
+ "Query will be ignored." % query_class.__name__)
+ for line in traceback.format_exc().split('\n'):
+ self.logger.error(line)
+
+ return Bcfg2.Server.Plugin.CallableDict(**data)
+
+ def start_client_run(self, metadata):
+ if Bcfg2.Options.setup.ldap_cache is None:
+ self.expire_cache(hostname=metadata.hostname)
+
+ def expire_cache(self, query=None, hostname=None):
+ """ Expire the cache. You can select the items to purge
+ per query and/or per host, or you can purge all cached
+ data. This is exposed as an XML-RPC RMI. """
+
+ tags = ['Ldap', 'results']
+ if query:
+ tags.append(query)
+ if hostname:
+ tags.append(hostname)
+
+ return Bcfg2.Server.Cache.expire(*tags)
+
+
+class LdapConnection(Debuggable):
+ """ Connection to an LDAP server. """
+
+ def __init__(self, host="localhost", port=389, uri=None, options=None,
binddn=None, bindpw=None):
+ Debuggable.__init__(self)
+
+ if HAS_LDAP:
+ msg = "Python ldap module is required for Ldap plugin"
+ self.logger.error(msg)
+ raise Bcfg2.Server.Plugin.PluginInitError(msg)
+
self.host = host
self.port = port
+ self.uri = uri
+ self.options = options
self.binddn = binddn
self.bindpw = bindpw
self.conn = None
+ self.__scopes__ = {
+ 'base': ldap.SCOPE_BASE,
+ 'one': ldap.SCOPE_ONELEVEL,
+ 'sub': ldap.SCOPE_SUBTREE,
+ }
+
def __del__(self):
+ """ Disconnection if the instance is destroyed. """
+ self.disconnect()
+
+ def disconnect(self):
+ """ If a connection to an LDAP server is available, disconnect it. """
if self.conn:
- self.conn.unbind()
+ self.conn.unbund()
+ self.conn = None
+
+ def connect(self):
+ """ Open a connection to the configured LDAP server, and do a simple
+ bind ff both binddn and bindpw are set. """
+ self.disconnect()
+ self.conn = ldap.initialize(self.get_uri())
+
+ if self.options is not None:
+ for (option, value) in self.options.items():
+ self.conn.set_option(option, value)
- 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):
- if attempt >= 1:
- logger.error("LdapPlugin error: " +
- "LDAP server down (retry " + str(attempt) + "/" +
- str(RETRY_COUNT) + ")")
+ """ Connect to the server and execute the query. If the server is
+ down, wait the configured amount and try to reconnect.
+
+ :param query: The query to execute on the LDAP server.
+ :type query: Bcfg.Server.Plugins.Ldap.LdapQuery
+ """
+ for attempt in range(Bcfg2.Options.setup.ldap_retries + 1):
try:
if not self.conn:
- self.init_conn()
- result = self.conn.search_s(
- query.base,
- SCOPE_MAP[query.scope],
- query.filter.replace("\\", "\\\\"),
- query.attrs,
- )
- break
+ self.connect()
+
+ return self.conn.search_s(
+ query.base, self.__scopes__[query.scope],
+ query.filter.replace('\\', '\\\\'), query.attrs)
+
except ldap.SERVER_DOWN:
self.conn = None
- time.sleep(RETRY_DELAY)
- return result
+ self.logger.error(
+ "LdapConnection: Server %s down. Retry %d/%d in %.2fs." %
+ (self.get_uri(), attempt + 1,
+ Bcfg2.Options.setup.ldap_retries,
+ Bcfg2.Options.setup.ldap_retry_delay))
+ time.sleep(Bcfg2.Options.setup.ldap_retry_delay)
+
+ return None
- @property
- def url(self):
- return "ldap://" + self.host + ":" + str(self.port)
+ def get_uri(self):
+ """ The URL of the LDAP server. """
+ if self.uri is None:
+ if self.port == 636:
+ return "ldaps://%s" % self.host
+ return "ldap://%s:%d" % (self.host, self.port)
+ return self.uri
class LdapQuery(object):
- """
- Query referencing an LdapConnection and providing several
- methods for query manipulation.
- """
+ """ Query referencing an LdapConnection and providing several
+ methods for query manipulation. """
+
+ #: Name of the Query, used to register it in additional data.
+ name = ClassName()
- name = "unknown"
base = ""
scope = "sub"
filter = "(objectClass=*)"
@@ -172,80 +267,48 @@ class LdapQuery(object):
result = None
def __unicode__(self):
- return "LdapQuery:" + self.name
+ return "LdapQuery: %s" % self.name
- def is_applicable(self, metadata):
- """
- Overrideable method to determine if the query is to be executed for
- the given metadata object.
- Defaults to true.
- """
- return True
+ def is_applicable(self, metadata): # pylint: disable=W0613
+ """ Check is the query should be executed for a given metadata
+ object.
- def prepare_query(self, metadata):
+ :param metadata: The client metadata
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
"""
- Overrideable method to alter the query based on metadata.
- Defaults to doing nothing.
-
- In most cases, you will do something like
+ return True
- self.filter = "(cn=" + metadata.hostname + ")"
+ def prepare_query(self, metadata, **kwargs): # pylint: disable=W0613
+ """ Prepares the query based on the client metadata. You can
+ for example modify the filter based on the client hostname.
- here.
+ :param metadata: The client metadata
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
"""
pass
- def process_result(self, metadata):
- """
- Overrideable method to post-process the query result.
- Defaults to returning the unaltered result.
- """
- return self.result
+ def process_result(self, metadata, **kwargs): # pylint: disable=W0613
+ """ Post-process the query result.
- def get_result(self, metadata):
- """
- Method to handle preparing, executing and processing the query.
- """
- if isinstance(self.connection, LdapConnection):
- self.prepare_query(metadata)
- self.result = self.connection.run_query(self)
- self.result = self.process_result(metadata)
- return self.result
- else:
- logger.error("LdapPlugin error: " +
- "No valid connection defined for query " + str(self))
- return None
-
-
-class LdapSubQuery(LdapQuery):
- """
- SubQueries are meant for internal use only and are not added
- to the metadata object. They are useful for situations where
- you need to run more than one query to obtain some data.
- """
- def prepare_query(self, metadata, **kwargs):
- """
- Overrideable method to alter the query based on metadata.
- 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.
+ :param metadata: The client metadata
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
"""
return self.result
def get_result(self, metadata, **kwargs):
+ """ Handle the perparation, execution and processing of the query.
+
+ :param metadata: The client metadata
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError`
"""
- Method to handle preparing, executing and processing the query.
- """
- if isinstance(self.connection, LdapConnection):
+
+ if self.connection is not None:
self.prepare_query(metadata, **kwargs)
self.result = self.connection.run_query(self)
- return self.process_result(metadata, **kwargs)
+ self.result = self.process_result(metadata, **kwargs)
else:
- logger.error("LdapPlugin error: " +
- "No valid connection defined for query " + str(self))
- return None
+ raise Bcfg2.Server.Plugin.PluginExecutionError(
+ 'No connection defined for %s' % self.name)
+
+ return self.result
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index b850c1870..b912d3725 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -21,24 +21,25 @@ from Bcfg2.Compat import MutableMapping, all, any, wraps
# pylint: enable=W0622
from Bcfg2.version import Bcfg2VersionInfo
+try:
+ from django.db import models
+ HAS_DJANGO = True
+except ImportError:
+ HAS_DJANGO = False
+
# pylint: disable=C0103
ClientVersions = None
MetadataClientModel = None
# pylint: enable=C0103
-HAS_DJANGO = False
def load_django_models():
""" Load models for Django after option parsing has completed """
# pylint: disable=W0602
- global MetadataClientModel, ClientVersions, HAS_DJANGO
+ global MetadataClientModel, ClientVersions
# pylint: enable=W0602
- try:
- from django.db import models
- HAS_DJANGO = True
- except ImportError:
- HAS_DJANGO = False
+ if not HAS_DJANGO:
return
class MetadataClientModel(models.Model, # pylint: disable=W0621
@@ -1394,8 +1395,6 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
# look at cert.cN
client = certinfo['commonName']
self.debug_log("Got cN %s; using as client name" % client)
- auth_type = self.auth.get(client,
- Bcfg2.Options.setup.authentication)
elif user == 'root':
id_method = 'address'
try:
@@ -1417,6 +1416,13 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
# we have the client name
self.debug_log("Authenticating client %s" % client)
+ # validate id_method
+ auth_type = self.auth.get(client, Bcfg2.Options.setup.authentication)
+ if auth_type == 'cert' and id_method != 'cert':
+ self.logger.error("Client %s does not provide a cert, but only "
+ "cert auth is allowed" % client)
+ return False
+
# next we validate the address
if (id_method != 'uuid' and
not self.validate_client_address(client, address)):
diff --git a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
index d3c38ef19..067e2faad 100644
--- a/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
+++ b/src/lib/Bcfg2/Server/Plugins/NagiosGen.py
@@ -92,17 +92,15 @@ class NagiosGen(Plugin, Generator):
for host in host_configs:
host_data.append(open(host, 'r').read())
- group_list = []
+ used_groups = set(['default'])
for line in "\n".join(host_data).splitlines():
# only include those groups which are actually used
if "hostgroup" in line:
- group_list += line.split()[1].split(',')
-
- group_list = list(set(group_list))
+ used_groups.update(line.split()[1].split(','))
for group in group_configs:
group_name = re.sub("(-group.cfg|.*/(?=[^/]+))", "", group)
- if group_name in group_list:
+ if group_name in used_groups:
groupfile = open(group, 'r')
group_data.append(groupfile.read())
groupfile.close()
diff --git a/src/lib/Bcfg2/Server/Plugins/Ohai.py b/src/lib/Bcfg2/Server/Plugins/Ohai.py
index 461be9ba8..b314e60a0 100644
--- a/src/lib/Bcfg2/Server/Plugins/Ohai.py
+++ b/src/lib/Bcfg2/Server/Plugins/Ohai.py
@@ -94,7 +94,12 @@ class Ohai(Bcfg2.Server.Plugin.Plugin,
return [self.probe]
def ReceiveData(self, meta, datalist):
- self.cache[meta.hostname] = datalist[0].text
+ if meta.hostname not in self.cache or \
+ self.cache[meta.hostname] != datalist[0].text:
+ self.cache[meta.hostname] = datalist[0].text
+
+ if self.core.metadata_cache_mode in ['cautious', 'aggressive']:
+ self.core.metadata_cache.expire(meta.hostname)
def get_additional_data(self, meta):
if meta.hostname in self.cache:
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
index 7de79e2f3..956cb9f51 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
@@ -6,6 +6,15 @@ from Bcfg2.Server.Plugins.Packages.Collection import Collection
from Bcfg2.Server.Plugins.Packages.Source import Source
+def strip_suffix(pkgname):
+ """ Remove the ':any' suffix from a dependency name if it is present.
+ """
+ if pkgname.endswith(':any'):
+ return pkgname[:-4]
+ else:
+ return pkgname
+
+
class AptCollection(Collection):
""" Handle collections of APT sources. This is a no-op object
that simply inherits from
@@ -34,8 +43,12 @@ class AptCollection(Collection):
for source in self:
if source.rawurl:
- self.logger.info("Packages: Skipping rawurl %s" %
- source.rawurl)
+ if source.rawurl[-1] != '/':
+ source.rawurl = source.rawurl + "/"
+ index = source.rawurl.rfind("/", 0, -1)
+ lines.append("deb %s %s" %
+ (source.rawurl[:index],
+ source.rawurl[index + 1:]))
else:
lines.append("deb %s %s %s" % (source.url, source.version,
" ".join(source.components)))
@@ -44,7 +57,7 @@ class AptCollection(Collection):
(source.url,
source.version,
" ".join(source.components)))
- lines.append("")
+ lines.append("")
return "\n".join(lines)
@@ -73,6 +86,7 @@ class AptSource(Source):
bdeps = dict()
brecs = dict()
bprov = dict()
+ self.pkgnames = set()
self.essentialpkgs = set()
for fname in self.files:
if not self.rawurl:
@@ -111,6 +125,7 @@ class AptSource(Source):
cdeps = [re.sub(r'\s+', '',
re.sub(r'\(.*\)', '', cdep))
for cdep in dep.split('|')]
+ cdeps = [strip_suffix(cdep) for cdep in cdeps]
dyn_dname = "choice-%s-%s-%s" % (pkgname,
barch,
vindex)
@@ -124,6 +139,7 @@ class AptSource(Source):
else:
raw_dep = re.sub(r'\(.*\)', '', dep)
raw_dep = raw_dep.rstrip().strip()
+ raw_dep = strip_suffix(raw_dep)
if words[0] == 'Recommends':
brecs[barch][pkgname].append(raw_dep)
else:
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
index 004e27874..e0d6e1fc3 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Collection.py
@@ -289,7 +289,7 @@ class Collection(list, Debuggable):
return any(source.is_virtual_package(self.metadata, package)
for source in self)
- def get_deps(self, package, recs=None):
+ def get_deps(self, package, recs=None, pinnings=None):
""" Get a list of the dependencies of the given package.
The base implementation simply aggregates the results of
@@ -297,16 +297,35 @@ class Collection(list, Debuggable):
:param package: The name of the symbol, but see :ref:`pkg-objects`
:type package: string
+ :param pinnings: Mapping from package names to source names.
+ :type pinnings: dict
:returns: list of strings, but see :ref:`pkg-objects`
"""
recommended = None
if recs and package in recs:
recommended = recs[package]
+ pin_found = False
+ pin_source = None
+ if pinnings and package in pinnings:
+ pin_source = pinnings[package]
+
for source in self:
+ if pin_source and source.name not in pin_source:
+ continue
+ pin_found = True
+
if source.is_package(self.metadata, package):
return source.get_deps(self.metadata, package, recommended)
+ if not pin_found:
+ if pin_source:
+ self.logger.error("Packages: Source '%s' for package '%s' not found" %
+ (' or '.join(pin_source), package))
+ else:
+ self.logger.error("Packages: No source found for package '%s'" %
+ package);
+
return []
def get_essential(self):
@@ -471,12 +490,14 @@ class Collection(list, Debuggable):
@track_statistics()
def complete(self, packagelist, # pylint: disable=R0912,R0914
- recommended=None):
+ recommended=None, pinnings=None):
""" Build a complete list of all packages and their dependencies.
:param packagelist: Set of initial packages computed from the
specification.
:type packagelist: set of strings, but see :ref:`pkg-objects`
+ :param pinnings: Mapping from package names to source names.
+ :type pinnings: dict
:returns: tuple of sets - The first element contains a set of
strings (but see :ref:`pkg-objects`) describing the
complete package list, and the second element is a
@@ -535,7 +556,7 @@ class Collection(list, Debuggable):
self.debug_log("Packages: handling package requirement %s" %
(current,))
packages.add(current)
- deps = self.get_deps(current, recommended)
+ deps = self.get_deps(current, recommended, pinnings)
newdeps = set(deps).difference(examined)
if newdeps:
self.debug_log("Packages: Package %s added requirements %s"
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Dummy.py b/src/lib/Bcfg2/Server/Plugins/Packages/Dummy.py
new file mode 100644
index 000000000..f47b8f22c
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Dummy.py
@@ -0,0 +1,35 @@
+""" Dummy backend for :mod:`Bcfg2.Server.Plugins.Packages` """
+
+from Bcfg2.Server.Plugins.Packages.Collection import Collection
+from Bcfg2.Server.Plugins.Packages.Source import Source
+
+
+class DummyCollection(Collection):
+ """ Handle collections of Dummy sources. This is a no-op object
+ that simply inherits from
+ :class:`Bcfg2.Server.Plugins.Packages.Collection.Collection`,
+ overrides nothing, and defers all operations to :class:`PacSource`
+ """
+
+ def __init__(self, metadata, sources, cachepath, basepath, debug=False):
+ # we define an __init__ that just calls the parent __init__,
+ # so that we can set the docstring on __init__ to something
+ # different from the parent __init__ -- namely, the parent
+ # __init__ docstring, minus everything after ``.. -----``,
+ # which we use to delineate the actual docs from the
+ # .. autoattribute hacks we have to do to get private
+ # attributes included in sphinx 1.0 """
+ Collection.__init__(self, metadata, sources, cachepath, basepath,
+ debug=debug)
+ __init__.__doc__ = Collection.__init__.__doc__.split(".. -----")[0]
+
+
+class DummySource(Source):
+ """ Handle Dummy sources """
+
+ #: DummySource sets the ``type`` on Package entries to "dummy"
+ ptype = 'dummy'
+
+ def __init__(self, basepath, xsource):
+ xsource.set('rawurl', 'http://example.com/')
+ Source.__init__(self, basepath, xsource)
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py b/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py
new file mode 100644
index 000000000..19877d32b
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Layman.py
@@ -0,0 +1,142 @@
+import os
+import layman
+import Bcfg2.Server.Plugin
+
+class LaymanSource(Bcfg2.Server.Plugin.Debuggable):
+ basegroups = ['portage', 'gentoo', 'emerge']
+ ptype = 'layman'
+ cclass = 'Portage'
+
+ def __init__(self, basepath, xsource):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ self.basepath = basepath
+ self.xsource = xsource
+
+ self.url = xsource.get('url', 'http://www.gentoo.org/proj/en/overlays/repositories.xml')
+ self.name = xsource.get('name', '')
+ self.priority = xsource.get('priority', 0)
+ self.cachefile = None
+ self.gpgkeys = []
+ self.recommended = False
+ self.essentialpkgs = set()
+ self.arches = [item.text for item in xsource.findall('Arch')]
+
+ self.url_map = [dict(version=None, component=None, arch=arch,
+ url=self.url, baseurl=self.url) for arch in self.arches]
+
+ #: A list of the the names of packages that are blacklisted
+ #: from this source
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+
+ #: A list of the the names of packages that are whitelisted in
+ #: this source
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+
+
+ # configure layman
+ base = os.path.join(basepath, 'layman')
+ storage = os.path.join(base, 'overlays')
+ config = layman.config.OptionConfig(options = {
+ 'storage': os.path.join(base, 'overlays'),
+ 'cache': os.path.join(base, 'cache'),
+ 'installed': os.path.join(base, 'installed.xml'),
+ 'local_list': os.path.join(base, 'overlays.xml'),
+ 'overlays': [self.url]
+ })
+ self.api = layman.LaymanAPI(config)
+
+ # path
+ self.dir = os.path.join(storage, self.name)
+
+ # build the set of conditions to see if this source applies to
+ # a given set of metadata
+ self.conditions = []
+ self.groups = [] # provided for some limited backwards compat
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+
+ def get_repo_name(self, url_map):
+ return self.name
+
+ def save_state(self):
+ pass
+
+ def load_state(self):
+ pass
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ return self.url
+ urls = property(get_urls)
+
+ def setup_data(self, force_update=False):
+ self.api.fetch_remote_list()
+ if not self.api.is_repo(self.name):
+ self.logger.error("Packages: Layman overlay '%s' not"
+ " found" % self.name)
+ return False
+
+ if not self.api.is_installed(self.name):
+ self.logger.info("Packages: Adding layman overlay '%s'" %
+ self.name)
+ if not self.api.add_repos(self.name):
+ self.logger.error("Packages: Failed adding layman"
+ " overlay '%s'" % self.name)
+ return False
+
+ if force_update:
+ if not self.api.sync(self.name):
+ self.logger.error("Packages: Failed syncing layman"
+ " overlay '%s'" % self.name)
+ return False
+
+ return True
+
+
+ def applies(self, metadata):
+ """ Return true if this source applies to the given client,
+ i.e., the client is in all necessary groups.
+
+ :param metadata: The client metadata to check to see if this
+ source applies
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: bool
+ """
+ # check arch groups
+ if not self.arch_groups_match(metadata):
+ return False
+
+ # check Group/Client tags from sources.xml
+ for condition in self.conditions:
+ if not condition(metadata):
+ return False
+
+ return True
+
+ def arch_groups_match(self, metadata):
+ """ Returns True if the client is in an arch group that
+ matches the arch of this source.
+
+ :returns: bool
+ """
+ for arch in self.arches:
+ if arch in metadata.groups:
+ return True
+ return False
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py
index 0e15d2e15..6fc084cc4 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py
@@ -1,10 +1,62 @@
""" Pacman backend for :mod:`Bcfg2.Server.Plugins.Packages` """
+import os
import tarfile
+from Bcfg2.Compat import cPickle
from Bcfg2.Server.Plugins.Packages.Collection import Collection
from Bcfg2.Server.Plugins.Packages.Source import Source
+def parse_db_file(pkgfile):
+ """ Parse a Pacman database file, returning a dictionary with
+ section headings for keys and lists of strings for values.
+ (Reference: ``sync_db_read`` in ``lib/libalpm/be_sync.c``)
+ """
+
+ pkg = {}
+ section = None
+
+ for line in pkgfile:
+ line = line.strip()
+
+ if section is not None:
+ if not line:
+ section = None
+ else:
+ pkg[section].append(line)
+ elif len(line) >= 2 and line[0] == line[-1] == '%':
+ section = line
+ pkg[section] = []
+
+ return pkg
+
+
+def parse_dep(dep):
+ """ Parse a Pacman dependency string, returning the package name,
+ version restriction (or ``None``), and description (or ``None``).
+ (Reference: ``alpm_dep_from_string`` in ``lib/libalpm/deps.c``)
+ """
+
+ rest_desc = dep.split(': ', 1)
+ if len(rest_desc) == 1:
+ rest, desc = rest_desc[0], None
+ else:
+ rest, desc = rest_desc
+
+ # Search for '=' last, since '<=' and '>=' are possible.
+ for symb in ['<', '>', '=']:
+ idx = rest.find(symb)
+ if idx >= 0:
+ name = rest[:idx]
+ version = rest[idx:]
+ break
+ else:
+ name = rest
+ version = None
+
+ return name, version, desc
+
+
class PacCollection(Collection):
""" Handle collections of Pacman sources. This is a no-op object
that simply inherits from
@@ -24,6 +76,10 @@ class PacCollection(Collection):
debug=debug)
__init__.__doc__ = Collection.__init__.__doc__.split(".. -----")[0]
+ @property
+ def __package_groups__(self):
+ return True
+
class PacSource(Source):
""" Handle Pacman sources """
@@ -31,6 +87,25 @@ class PacSource(Source):
#: PacSource sets the ``type`` on Package entries to "pacman"
ptype = 'pacman'
+ def __init__(self, basepath, xsource):
+ self.pacgroups = {}
+
+ Source.__init__(self, basepath, xsource)
+ __init__.__doc__ = Source.__init__.__doc__
+
+ def load_state(self):
+ data = open(self.cachefile, 'rb')
+ (self.pkgnames, self.deps, self.provides,
+ self.recommends, self.pacgroups) = cPickle.load(data)
+ load_state.__doc__ = Source.load_state.__doc__
+
+ def save_state(self):
+ cache = open(self.cachefile, 'wb')
+ cPickle.dump((self.pkgnames, self.deps, self.provides,
+ self.recommends, self.pacgroups), cache, 2)
+ cache.close()
+ save_state.__doc__ = Source.save_state.__doc__
+
@property
def urls(self):
""" A list of URLs to the base metadata file for each
@@ -45,14 +120,12 @@ class PacSource(Source):
else:
raise Exception("PacSource : RAWUrl not supported (yet)")
- def read_files(self):
- bdeps = dict()
- bprov = dict()
-
- depfnames = ['Depends', 'Pre-Depends']
- if self.recommended:
- depfnames.append('Recommends')
-
+ def read_files(self): # pylint: disable=R0912
+ bdeps = {}
+ brecs = {}
+ bprov = {}
+ self.pkgnames = set()
+ self.pacgroups = {}
for fname in self.files:
if not self.rawurl:
barch = [x for x in fname.split('@') if x in self.arches][0]
@@ -62,8 +135,9 @@ class PacSource(Source):
barch = self.arches[0]
if barch not in bdeps:
- bdeps[barch] = dict()
- bprov[barch] = dict()
+ bdeps[barch] = {}
+ brecs[barch] = {}
+ bprov[barch] = {}
try:
self.debug_log("Packages: try to read %s" % fname)
tar = tarfile.open(fname, "r")
@@ -71,11 +145,52 @@ class PacSource(Source):
self.logger.error("Packages: Failed to read file %s" % fname)
raise
+ packages = {}
for tarinfo in tar:
- if tarinfo.isdir():
- self.pkgnames.add(tarinfo.name.rsplit("-", 2)[0])
- self.debug_log("Packages: added %s" %
- tarinfo.name.rsplit("-", 2)[0])
+ if not tarinfo.isfile():
+ continue
+ prefix = os.path.dirname(tarinfo.name)
+ if prefix not in packages:
+ packages[prefix] = {}
+ pkg = parse_db_file(tar.extractfile(tarinfo))
+ packages[prefix].update(pkg)
+
+ for pkg in packages.values():
+ pkgname = pkg['%NAME%'][0]
+ self.pkgnames.add(pkgname)
+ bdeps[barch][pkgname] = []
+ brecs[barch][pkgname] = []
+
+ if '%DEPENDS%' in pkg:
+ for dep in pkg['%DEPENDS%']:
+ dname = parse_dep(dep)[0]
+ bdeps[barch][pkgname].append(dname)
+
+ if '%OPTDEPENDS%' in pkg:
+ for dep in pkg['%OPTDEPENDS%']:
+ dname = parse_dep(dep)[0]
+ brecs[barch][pkgname].append(dname)
+
+ if '%PROVIDES%' in pkg:
+ for dep in pkg['%PROVIDES%']:
+ dname = parse_dep(dep)[0]
+ if dname not in bprov[barch]:
+ bprov[barch][dname] = set()
+ bprov[barch][dname].add(pkgname)
+
+ if '%GROUPS%' in pkg:
+ for group in pkg['%GROUPS%']:
+ if group not in self.pacgroups:
+ self.pacgroups[group] = []
+ self.pacgroups[group].append(pkgname)
+
tar.close()
- self.process_files(bdeps, bprov)
+ self.process_files(bdeps, bprov, brecs)
read_files.__doc__ = Source.read_files.__doc__
+
+ def get_group(self, metadata, group, ptype=None):
+ try:
+ return self.pacgroups[group]
+ except KeyError:
+ return []
+ get_group.__doc__ = Source.get_group.__doc__
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
index 1af046ec0..9db521aae 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/PackagesSources.py
@@ -106,6 +106,8 @@ class PackagesSources(Bcfg2.Server.Plugin.StructFile):
source = self.source_from_xml(xsource)
if source is not None:
self.entries.append(source)
+ sorted(self.entries, key=(lambda source: source.priority),
+ reverse=True)
Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__ + """
``Index`` is responsible for calling :func:`source_from_xml`
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py
index 736cdcdd4..55dd4e488 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py
@@ -56,6 +56,7 @@ class PkgngSource(Source):
def read_files(self):
bdeps = dict()
+ self.pkgnames = set()
for fname in self.files:
if not self.rawurl:
abi = [x
@@ -75,9 +76,7 @@ class PkgngSource(Source):
self.logger.error("Packages: Failed to read file %s" % fname)
raise
for line in reader.readlines():
- if not isinstance(line, str):
- line = line.decode('utf-8')
- pkg = json.loads(line)
+ pkg = json.loads(unicode(line, errors='ignore'))
pkgname = pkg['name']
self.pkgnames.add(pkgname)
if 'deps' in pkg:
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py b/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py
new file mode 100644
index 000000000..9df4467e0
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Portage.py
@@ -0,0 +1,333 @@
+import re
+import gzip
+import sys
+import os
+import lxml.etree
+import Bcfg2.Options
+import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugins.Packages.Collection import Collection
+from Bcfg2.Server.Plugins.Packages.Layman import LaymanSource
+
+_portage_python = '/usr/lib/portage/pym/'
+
+def _import_portage(caller):
+ # generate prefix path
+ caller.prefix = os.path.join(caller.basepath, 'cache', 'portage')
+ if not os.path.isdir(caller.prefix):
+ caller.logger.error("Packages: %s is not a dir. "
+ "Portage will not work. Please "
+ "remember to setup a EPREFIX there." %
+ caller.prefix)
+ # TODO: automatic EPREFIX setup
+ raise Exception('Invalid EPREFIX')
+
+ os.environ['PORTAGE_OVERRIDE_EPREFIX'] = caller.prefix
+
+ if not os.path.isdir(_portage_python):
+ self.logger.error("Packages: %s not found. Have you installed "
+ "the portage python modules?" % _portage_python)
+ raise Exception('Portage not found')
+
+ sys.path = sys.path + [_portage_python]
+ portage = __import__('portage', globals(), locals(),
+ ['_sets', 'dbapi.porttree' ])
+ emerge = __import__('_emerge', globals(), locals(),
+ ['RootConfig', 'depgraph', 'Package', 'actions'])
+
+ # setup profile
+ if '_setup_profile' in dir(caller):
+ caller._setup_profile(portage)
+
+ # fix some default settings
+ portage.settings.unlock()
+ portage.settings['PORTAGE_RSYNC_INITIAL_TIMEOUT'] = '0'
+ portage.settings.lock()
+
+ porttree = portage.db[portage.root]['porttree']
+ caller._import_portage(portage, emerge, porttree)
+
+
+class PortageCollection(Collection):
+ def __init__(self, metadata, sources, cachepath, basepath, debug=False):
+ Collection.__init__(self, metadata, sources, cachepath, basepath,
+ debug=debug)
+ self.portage = None
+ self.emerge = None
+ self.porttree = None
+
+ @property
+ def cachefiles(self):
+ return []
+
+ def complete(self, packagelist, pinnings=None, recommended=None):
+ if not self.portage:
+ _import_portage(self)
+
+ # calculate deps
+ setconfig = self.portage._sets.load_default_config(
+ self.portage.settings,
+ self.portage.db[self.portage.root])
+ rconfig = self.emerge.RootConfig.RootConfig(
+ self.portage.settings,
+ self.portage.db[self.portage.root],
+ setconfig)
+ self.portage.db[self.portage.root]['root_config'] = rconfig
+
+ pkgs = ["=" + j.cpv for (i, j) in packagelist if i == 'ok']
+ fail = [j for (i, j) in packagelist if i == 'fail']
+
+ x = self.emerge.depgraph.backtrack_depgraph(
+ self.portage.settings,
+ self.portage.db,
+ {'--pretend': True},
+ {'recurse': True},
+ 'merge',
+ pkgs,
+ None)
+
+ g = x[1]._dynamic_config.digraph
+ packages = [i for i in g.all_nodes() \
+ if isinstance(i, self.emerge.Package.Package)]
+
+ return (set(packages), set(fail))
+
+ def get_additional_data(self):
+ return []
+
+ def get_group(self, group):
+ self.logger.warning("Packages: Package sets are currently not supported")
+ return []
+
+ def packages_from_entry(self, entry):
+ if not self.portage:
+ _import_portage(self)
+
+ try:
+ name = entry.get('name')
+ pkgs = self.porttree.dep_bestmatch(name)
+ except self.portage.exception.AmbiguousPackageName as e:
+ self.logger.error("Packages: AmbiguousPackageName: %s" % e)
+ pkgs = ''
+
+ if pkgs == '':
+ return [('fail', name)]
+
+ return [('ok', pkgs)]
+
+ def packages_to_entry(self, pkgs, entry):
+ for pkg in pkgs:
+ if pkg.slot != '0':
+ name = "%s:%s" % (pkg.cp, pkg.slot)
+ else:
+ name = pkg.cp
+
+ lxml.etree.SubElement(entry, 'BoundPackage', name=name,
+ version=Bcfg2.Options.setup.packages_version,
+ type=self.ptype, origin='Packages')
+
+ def get_new_packages(self, initial, complete):
+ new = []
+ init = [pkg.cp for status, pkg in initial if status == 'ok']
+
+ for pkg in complete:
+ if pkg.cp not in init:
+ new.append(pkg)
+
+ return new
+
+ def setup_data(self, force_update=False):
+ pass
+
+ def _setup_profile(self, portage):
+ if 'gentoo-profile' not in self.metadata.Probes:
+ raise Exception('Unknown profile.')
+
+ profile = os.path.join(self.prefix, 'usr/portage/profiles/',
+ self.metadata.Probes['gentoo-profile'].strip())
+
+ env = portage.settings.configdict['backupenv']
+
+ # add layman overlays
+ env['PORTDIR_OVERLAY'] = ''
+ for overlay in self:
+ if isinstance(overlay, LaymanSource):
+ env['PORTDIR_OVERLAY'] += ' '
+ env['PORTDIR_OVERLAY'] += overlay.dir
+
+ portage.settings = portage.package.ebuild.config.config(
+ config_root=portage.settings['PORTAGE_CONFIGROOT'],
+ target_root=portage.settings['ROOT'],
+ env=env,
+ eprefix=portage.settings['EPREFIX'],
+ config_profile_path=profile)
+
+ portage.db[portage.root]['porttree'] \
+ = portage.dbapi.porttree.portagetree(portage.settings)
+ portage.db[portage.root]['vartree'] \
+ = portage.dbapi.vartree.vartree(portage.settings)
+
+ def _set_portage_config(self):
+ # get global use flags
+ self.portage.settings.unlock()
+ self.portage.settings['USE'] = ''
+ if 'gentoo-use-flags' in self.metadata.Probes:
+ self.portage.settings['USE'] = \
+ self.metadata.Probes['gentoo-use-flags']
+
+ # add package flags (accept_keywords, use)
+ if hasattr(self.metadata, 'PkgVars'):
+ for k in self.metadata.PkgVars['keywords']:
+ keyword = self.metadata.PkgVars['keywords'][k]
+ self.portage.settings._keywords_manager.pkeywordsdict[k] = \
+ {self.portage.dep.Atom(k): tuple(keyword)}
+
+
+ for u in self.metadata.PkgVars['use']:
+ use = self.metadata.PkgVars['use'][u]
+ self.portage.settings._use_manager._pusedict[u] = \
+ {u: tuple(use)}
+
+ self.portage.settings.lock()
+
+ def _import_portage(self, portage, emerge, porttree):
+ self.portage = portage
+ self.emerge = emerge
+ self.porttree = porttree
+ self._set_portage_config()
+
+ for s in self:
+ if isinstance(s, PortageSource):
+ s._import_portage(portage, emerge, porttree)
+
+
+class PortageSource(Bcfg2.Server.Plugin.Debuggable):
+ basegroups = ['portage', 'gentoo', 'emerge']
+ ptype = 'ebuild'
+
+ def __init__(self, basepath, xsource):
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
+ self.basepath = basepath
+ self.xsource = xsource
+
+ self.url = xsource.get('url', '')
+ self.priority = xsource.get('priority', 0)
+ self.cachefile = None
+ self.gpgkeys = []
+ self.recommended = False
+ self.essentialpkgs = set()
+ self.arches = [item.text for item in xsource.findall('Arch')]
+
+ self.url_map = [dict(version=None, component=None, arch=arch,
+ url=self.url, baseurl=self.url) for arch in self.arches]
+
+ #: A list of the the names of packages that are blacklisted
+ #: from this source
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+
+ #: A list of the the names of packages that are whitelisted in
+ #: this source
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+
+
+ self.portage = None
+ self.emerge = None
+ self.porttree = None
+
+ # build the set of conditions to see if this source applies to
+ # a given set of metadata
+ self.conditions = []
+ self.groups = [] # provided for some limited backwards compat
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+ def get_repo_name(self, url_map):
+ return "portage"
+
+ def _import_portage(self, portage, emerge, porttree):
+ self.portage = portage
+ self.emerge = emerge
+ self.porttree = porttree
+
+ def save_state(self):
+ pass
+
+ def load_state(self):
+ pass
+
+ def filter_unknown(self, unknown):
+ filtered = set([u for u in unknown if u.startswith('choice')])
+ unknown.difference_update(filtered)
+
+ def get_urls(self):
+ return self.url
+ urls = property(get_urls)
+
+ def setup_data(self, force_update=False):
+ if not self.porttree:
+ _import_portage(self)
+
+ timestamp = os.path.join(self.porttree.portroot, 'metadata/timestamp.chk')
+ if not os.path.isfile(timestamp):
+ self.logger.error("Packages: Timestamp not found; "
+ "falling back to sync")
+ force_update = True
+
+ if force_update:
+ # update sync url
+ self.portage.settings.unlock()
+ self.portage.settings['SYNC'] = self.url
+ self.portage.settings.lock()
+
+ # realy force the sync
+ if os.path.isfile(timestamp):
+ os.unlink(timestamp)
+
+ # sync
+ self.logger.info("Packages: Syncing with %s" % self.url)
+ self.emerge.actions.action_sync(self.portage.settings,
+ self.portage.db, None,
+ {'--quiet': True}, 'sync')
+
+ def applies(self, metadata):
+ """ Return true if this source applies to the given client,
+ i.e., the client is in all necessary groups.
+
+ :param metadata: The client metadata to check to see if this
+ source applies
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: bool
+ """
+ # check arch groups
+ if not self.arch_groups_match(metadata):
+ return False
+
+ # check Group/Client tags from sources.xml
+ for condition in self.conditions:
+ if not condition(metadata):
+ return False
+
+ return True
+
+ def arch_groups_match(self, metadata):
+ """ Returns True if the client is in an arch group that
+ matches the arch of this source.
+
+ :returns: bool
+ """
+ for arch in self.arches:
+ if arch in metadata.groups:
+ return True
+ return False
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
index c9f6ea14a..5ed809694 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py
@@ -135,22 +135,22 @@ class Source(Debuggable): # pylint: disable=R0902
#: A list of the text of all 'Component' attributes of this
#: source from XML
- self.components = [item.text for item in xsource.findall('Component')]
+ self.components = []
#: A list of the arches supported by this source
- self.arches = [item.text for item in xsource.findall('Arch')]
+ self.arches = []
#: A list of the the names of packages that are blacklisted
#: from this source
- self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+ self.blacklist = []
#: A list of the the names of packages that are whitelisted in
#: this source
- self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+ self.whitelist = []
#: Whether or not to include deb-src lines in the generated APT
#: configuration
- self.debsrc = xsource.get('debsrc', 'false') == 'true'
+ self.debsrc = False
#: A dict of repository options that will be included in the
#: configuration generated on the server side (if such is
@@ -162,51 +162,44 @@ class Source(Debuggable): # pylint: disable=R0902
#: configuration generated for the client (if that is
#: supported by the backend)
self.client_options = dict()
- opts = xsource.findall("Options")
- for el in opts:
- repoopts = dict([(k, v)
- for k, v in el.attrib.items()
- if k != "clientonly" and k != "serveronly"])
- if el.get("clientonly", "false").lower() == "false":
- self.server_options.update(repoopts)
- if el.get("serveronly", "false").lower() == "false":
- self.client_options.update(repoopts)
#: A list of URLs to GPG keys that apply to this source
- self.gpgkeys = [el.text for el in xsource.findall("GPGKey")]
+ self.gpgkeys = []
#: Whether or not to include essential packages from this source
- self.essential = xsource.get('essential', 'true').lower() == 'true'
+ self.essential = True
#: Whether or not to include recommended packages from this source
- self.recommended = xsource.get('recommended',
- 'false').lower() == 'true'
+ self.recommended = False
#: The "rawurl" attribute from :attr:`xsource`, if applicable.
#: A trailing slash is automatically appended to this if there
#: wasn't one already present.
- self.rawurl = xsource.get('rawurl', '')
- if self.rawurl and not self.rawurl.endswith("/"):
- self.rawurl += "/"
+ self.rawurl = None
#: The "url" attribute from :attr:`xsource`, if applicable. A
#: trailing slash is automatically appended to this if there
#: wasn't one already present.
- self.url = xsource.get('url', '')
- if self.url and not self.url.endswith("/"):
- self.url += "/"
+ self.url = None
#: The "version" attribute from :attr:`xsource`
- self.version = xsource.get('version', '')
+ self.version = None
#: The "name" attribute from :attr:`xsource`
- self.name = xsource.get('name', None)
+ self.name = None
+
+ #: The "priority" attribute from :attr:`xsource`
+ self.priority = xsource.get('priority', 500)
+
+ #: The "pin" attribute from :attr:`xsource`
+ self.pin = xsource.get('pin', '')
#: A list of predicates that are used to determine if this
#: source applies to a given
#: :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata`
#: object.
self.conditions = []
+
#: Formerly, :ref:`server-plugins-generators-packages` only
#: supported applying package sources to groups; that is, they
#: could not be assigned by more complicated logic like
@@ -214,22 +207,6 @@ class Source(Debuggable): # pylint: disable=R0902
#: attribute attempts to provide for some limited backwards
#: compat with older code that relies on this.
self.groups = []
- for el in xsource.iterancestors():
- if el.tag == "Group":
- if el.get("negate", "false").lower() == "true":
- self.conditions.append(lambda m, el=el:
- el.get("name") not in m.groups)
- else:
- self.groups.append(el.get("name"))
- self.conditions.append(lambda m, el=el:
- el.get("name") in m.groups)
- elif el.tag == "Client":
- if el.get("negate", "false").lower() == "true":
- self.conditions.append(lambda m, el=el:
- el.get("name") != m.hostname)
- else:
- self.conditions.append(lambda m, el=el:
- el.get("name") == m.hostname)
#: A set of all package names in this source. This will not
#: necessarily be populated, particularly by backends that
@@ -253,6 +230,8 @@ class Source(Debuggable): # pylint: disable=R0902
#: symbols>``. This will not necessarily be populated.
self.recommends = dict()
+ self._init_attributes(xsource)
+
#: The file (or directory) used for this source's cache data
self.cachefile = os.path.join(self.basepath,
"cache-%s" % self.cachekey)
@@ -277,11 +256,13 @@ class Source(Debuggable): # pylint: disable=R0902
for arch in self.arches:
if self.url:
usettings = [dict(version=self.version, component=comp,
- arch=arch, debsrc=self.debsrc)
+ arch=arch, debsrc=self.debsrc,
+ priority=self.priority, pin=self.pin)
for comp in self.components]
else: # rawurl given
usettings = [dict(version=self.version, component=None,
- arch=arch, debsrc=self.debsrc)]
+ arch=arch, debsrc=self.debsrc,
+ priority=self.priority, pin=self.pin)]
for setting in usettings:
if not self.rawurl:
@@ -290,8 +271,73 @@ class Source(Debuggable): # pylint: disable=R0902
setting['baseurl'] = self.rawurl
setting['url'] = baseurl % setting
setting['name'] = self.get_repo_name(setting)
+ setting['options'] = dict(server=self.server_options,
+ client=self.client_options)
self.url_map.extend(usettings)
+ def _init_attributes(self, xsource):
+ """
+ This functions evaluates the Source tag and parses all
+ attributes. Override this function in a sub class to
+ parse specific attributes. Do not use ``__init__`` because
+ ``Source.__init__`` may call other functions that already
+ need this specific fields. This functions is called before
+ any other function.
+
+ :param xsource: The XML tag that describes this source
+ :type source: lxml.etree._Element
+ """
+
+ self.components = [item.text for item in xsource.findall('Component')]
+ self.arches = [item.text for item in xsource.findall('Arch')]
+ self.blacklist = [item.text for item in xsource.findall('Blacklist')]
+ self.whitelist = [item.text for item in xsource.findall('Whitelist')]
+ self.debsrc = xsource.get('debsrc', 'false') == 'true'
+
+ opts = xsource.findall("Options")
+ for el in opts:
+ repoopts = dict([(k, v)
+ for k, v in el.attrib.items()
+ if k != "clientonly" and k != "serveronly"])
+ if el.get("clientonly", "false").lower() == "false":
+ self.server_options.update(repoopts)
+ if el.get("serveronly", "false").lower() == "false":
+ self.client_options.update(repoopts)
+
+ 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 += "/"
+
+ self.url = xsource.get('url', '')
+ if self.url and not self.url.endswith("/"):
+ self.url += "/"
+
+ self.version = xsource.get('version', '')
+ self.name = xsource.get('name', None)
+
+ for el in xsource.iterancestors():
+ if el.tag == "Group":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") not in m.groups)
+ else:
+ self.groups.append(el.get("name"))
+ self.conditions.append(lambda m, el=el:
+ el.get("name") in m.groups)
+ elif el.tag == "Client":
+ if el.get("negate", "false").lower() == "true":
+ self.conditions.append(lambda m, el=el:
+ el.get("name") != m.hostname)
+ else:
+ self.conditions.append(lambda m, el=el:
+ el.get("name") == m.hostname)
+
@property
def cachekey(self):
""" A unique key for this source that will be used to generate
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index dbe3f9ce5..acb11f1ab 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -456,16 +456,13 @@ class YumCollection(Collection):
reponame = basereponame
added = False
+ rid = 1
while not added:
try:
config.add_section(reponame)
added = True
except ConfigParser.DuplicateSectionError:
- match = re.search(r'-(\d+)', reponame)
- if match:
- rid = int(match.group(1)) + 1
- else:
- rid = 1
+ rid += 1
reponame = "%s-%d" % (basereponame, rid)
config.set(reponame, "name", reponame)
@@ -853,7 +850,7 @@ class YumCollection(Collection):
return new
@track_statistics()
- def complete(self, packagelist, recommended=None):
+ def complete(self, packagelist, recommended=None, pinnings=None):
""" Build a complete list of all packages and their dependencies.
When using the Python yum libraries, this defers to the
@@ -871,7 +868,8 @@ class YumCollection(Collection):
resolved.
"""
if not self.use_yum:
- return Collection.complete(self, packagelist, recommended)
+ return Collection.complete(self, packagelist, recommended,
+ pinnings)
lock = FileLock(os.path.join(self.cachefile, "lock"))
slept = 0
@@ -1004,8 +1002,20 @@ class YumSource(Source):
ptype = 'yum'
def __init__(self, basepath, xsource):
- Source.__init__(self, basepath, xsource)
+ self.filemap = dict()
+ self.file_to_arch = dict()
+ self.needed_paths = set()
+ self.packages = dict()
+ self.yumgroups = dict()
self.pulp_id = None
+ self.repo = None
+
+ Source.__init__(self, basepath, xsource)
+ __init__.__doc__ = Source.__init__.__doc__
+
+ def _init_attributes(self, xsource):
+ Source._init_attributes(self, xsource)
+
if HAS_PULP and xsource.get("pulp_id"):
self.pulp_id = xsource.get("pulp_id")
@@ -1034,15 +1044,11 @@ class YumSource(Source):
self.repo['relative_path'])
self.arches = [self.repo['arch']]
- self.packages = dict()
self.deps = dict([('global', dict())])
self.provides = dict([('global', dict())])
self.filemap = dict([(x, dict())
for x in ['global'] + self.arches])
- self.needed_paths = set()
- self.file_to_arch = dict()
- self.yumgroups = dict()
- __init__.__doc__ = Source.__init__.__doc__
+ _init_attributes.__doc__ = Source._init_attributes.__doc__
@property
def use_yum(self):
@@ -1130,6 +1136,94 @@ class YumSource(Source):
self.file_to_arch[self.escape_url(fullurl)] = arch
return urls
+ # pylint: disable=R0911,R0912
+ # disabling the pylint errors above because we are interesting in
+ # replicating the flow of the RPM code.
+ def _compare_rpm_versions(self, str1, str2):
+ """ Compare RPM versions.
+
+ This is an attempt to reimplement RPM's rpmvercmp method in python.
+
+ :param str1: package 1 version string
+ :param str2: package 2 version string
+ :return: 1 - str1 is newer than str2
+ 0 - str1 and str2 are the same version
+ -1 - str2 is newer than str1"""
+ if str1 == str2:
+ return 0
+
+ front_strip_re = re.compile('^[^A-Za-z0-9~]+')
+ risdigit = re.compile('(^[0-9]+)')
+ risalpha = re.compile('(^[A-Za-z])')
+ lzeroes = re.compile('^0+')
+
+ while len(str1) > 0 or len(str2) > 0:
+ str1 = front_strip_re.sub('', str1)
+ str2 = front_strip_re.sub('', str2)
+
+ if len(str1) == 0 or len(str2) == 0:
+ break
+
+ # handle the tilde separator
+ if str1[0] == '~' and str2[0] == '~':
+ str1 = str1[1:]
+ str2 = str2[1:]
+ elif str1[0] == '~':
+ return -1
+ elif str2[0] == '~':
+ return 1
+
+ # grab continuous segments from each string
+ isnum = False
+ if risdigit.match(str1):
+ segment1 = risdigit.split(str1)[1]
+ str1 = risdigit.split(str1)[2]
+ if risdigit.match(str2):
+ segment2 = risdigit.split(str2)[1]
+ str2 = risdigit.split(str2)[2]
+ else:
+ segment2 = ''
+ isnum = True
+ else:
+ segment1 = risalpha.split(str1)[1]
+ str1 = risalpha.split(str1)[2]
+ if risalpha.match(str2):
+ segment2 = risalpha.split(str2)[1]
+ str2 = risalpha.split(str2)[2]
+ else:
+ segment2 = ''
+
+ # numeric segments are always newer than alpha segments
+ if len(segment2) == 0:
+ if isnum:
+ return 1
+ return -1
+
+ if isnum:
+ # discard leading zeroes
+ segment1 = lzeroes.sub('', segment1)
+ segment2 = lzeroes.sub('', segment2)
+ # higher number has more digits
+ if len(segment1) > len(segment2):
+ return 1
+ elif len(segment2) > len(segment1):
+ return -1
+ # do a simple string comparison
+ if segment1 > segment2:
+ return 1
+ elif segment2 > segment1:
+ return -1
+
+ # if one of the strings is empty, the version of the longer
+ # string is higher
+ if len(str1) > len(str2):
+ return 1
+ elif len(str2) > len(str1):
+ return -1
+ else:
+ return 0
+ # pylint: enable=R0911,R0912
+
@track_statistics()
def read_files(self):
""" When using the builtin yum parser, read and parse locally
@@ -1198,13 +1292,33 @@ class YumSource(Source):
if arch not in self.packages:
self.packages[arch] = set()
if arch not in self.deps:
- self.deps[arch] = dict()
+ self.deps[arch] = {}
if arch not in self.provides:
- self.provides[arch] = dict()
+ self.provides[arch] = {}
+ versionmap = {}
for pkg in data.getchildren():
if not pkg.tag.endswith('package'):
continue
pkgname = pkg.find(XP + 'name').text
+ vtag = pkg.find(XP + 'version')
+ epoch = vtag.get('epoch')
+ version = vtag.get('ver')
+ release = vtag.get('rel')
+ if pkgname in self.packages[arch]:
+ # skip if version older than a previous version
+ if (self._compare_rpm_versions(
+ epoch, versionmap[pkgname]['epoch']) < 0):
+ continue
+ elif (self._compare_rpm_versions(
+ version, versionmap[pkgname]['version']) < 0):
+ continue
+ elif (self._compare_rpm_versions(
+ release, versionmap[pkgname]['release']) < 0):
+ continue
+ versionmap[pkgname] = {}
+ versionmap[pkgname]['epoch'] = epoch
+ versionmap[pkgname]['version'] = version
+ versionmap[pkgname]['release'] = release
self.packages[arch].add(pkgname)
pdata = pkg.find(XP + 'format')
@@ -1256,10 +1370,15 @@ class YumSource(Source):
arch = [a for a in self.arches if a in metadata.groups]
if not arch:
return False
- return ((package in self.packages['global'] or
- package in self.packages[arch[0]]) and
- package not in self.blacklist and
- (len(self.whitelist) == 0 or package in self.whitelist))
+ try:
+ return ((package in self.packages['global'] or
+ package in self.packages[arch[0]]) and
+ package not in self.blacklist and
+ (len(self.whitelist) == 0 or package in self.whitelist))
+ except KeyError:
+ self.logger.debug("Packages: Unable to find %s for arch %s" %
+ (package, arch[0]))
+ return False
is_package.__doc__ = Source.is_package.__doc__
def get_vpkgs(self, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py b/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py
index b2e43bde7..89cc23090 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py
@@ -274,29 +274,31 @@ class HelperSubcommand(Bcfg2.Options.Subcommand):
# whether or not this command accepts input on stdin
accept_input = True
- def __init__(self):
- Bcfg2.Options.Subcommand.__init__(self)
- self.verbosity = 0
+ # logging level
+ verbosity = 0
+
+ def run(self, setup):
if Bcfg2.Options.setup.debug:
self.verbosity = 5
elif Bcfg2.Options.setup.verbose:
self.verbosity = 1
- def run(self, setup):
- try:
- data = json.loads(sys.stdin.read())
- except ValueError:
- self.logger.error("Error decoding JSON input: %s" %
- sys.exc_info()[1])
- print(json.dumps(self.fallback))
- return 2
+ data = None
+ if self.accept_input:
+ try:
+ data = json.loads(sys.stdin.read())
+ except ValueError:
+ self.logger.error("Error decoding JSON input: %s" %
+ sys.exc_info()[1])
+ print(json.dumps(self.fallback))
+ return 2
try:
print(json.dumps(self._run(setup, data)))
except: # pylint: disable=W0702
self.logger.error("Unexpected error running %s: %s" %
- self.__class__.__name__.lower(),
- sys.exc_info()[1], exc_info=1)
+ (self.__class__.__name__.lower(),
+ sys.exc_info()[1]), exc_info=1)
print(json.dumps(self.fallback))
return 2
return 0
@@ -310,10 +312,13 @@ class DepSolverSubcommand(HelperSubcommand): # pylint: disable=W0223
""" Base class for helper commands that use the depsolver (i.e.,
only resolve dependencies, don't modify the cache) """
- def __init__(self):
- HelperSubcommand.__init__(self)
+ # DepSolver instance used in _run function
+ depsolver = None
+
+ def run(self, setup):
self.depsolver = DepSolver(Bcfg2.Options.setup.yum_config,
self.verbosity)
+ HelperSubcommand.run(self, setup)
class CacheManagerSubcommand(HelperSubcommand): # pylint: disable=W0223
@@ -322,10 +327,13 @@ class CacheManagerSubcommand(HelperSubcommand): # pylint: disable=W0223
fallback = False
accept_input = False
- def __init__(self):
- HelperSubcommand.__init__(self)
+ # CacheManager instance used in _run function
+ cachemgr = None
+
+ def run(self, setup):
self.cachemgr = CacheManager(Bcfg2.Options.setup.yum_config,
self.verbosity)
+ HelperSubcommand.run(self, setup)
class Clean(CacheManagerSubcommand):
@@ -376,10 +384,7 @@ class CLI(Bcfg2.Options.CommandRegistry):
""" The bcfg2-yum-helper CLI """
options = [
Bcfg2.Options.PathOption(
- "-c", "--yum-config", help="Yum config file"),
- Bcfg2.Options.PositionalArgument(
- "command", help="Yum helper command",
- choices=['clean', 'complete', 'get_groups'])]
+ "-c", "--yum-config", help="Yum config file")]
def __init__(self):
Bcfg2.Options.CommandRegistry.__init__(self)
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
index 3aa5c415f..3bdfddf31 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -10,7 +10,7 @@ import lxml.etree
import Bcfg2.Options
import Bcfg2.Server.Cache
import Bcfg2.Server.Plugin
-from Bcfg2.Compat import urlopen, HTTPError, URLError, MutableMapping
+from Bcfg2.Compat import urlopen, HTTPError, URLError
from Bcfg2.Server.Plugins.Packages.Collection import Collection, \
get_collection_class
from Bcfg2.Server.Plugins.Packages.PackagesSources import PackagesSources
@@ -36,52 +36,6 @@ class PackagesBackendAction(Bcfg2.Options.ComponentAction):
fail_silently = True
-class OnDemandDict(MutableMapping):
- """ This maps a set of keys to a set of value-getting functions;
- the values are populated on-the-fly by the functions as the values
- are needed (and not before). This is used by
- :func:`Bcfg2.Server.Plugins.Packages.Packages.get_additional_data`;
- see the docstring for that function for details on why.
-
- Unlike a dict, you should not specify values for for the righthand
- side of this mapping, but functions that get values. E.g.:
-
- .. code-block:: python
-
- d = OnDemandDict(foo=load_foo,
- bar=lambda: "bar");
- """
-
- def __init__(self, **getters):
- self._values = dict()
- self._getters = dict(**getters)
-
- def __getitem__(self, key):
- if key not in self._values:
- self._values[key] = self._getters[key]()
- return self._values[key]
-
- def __setitem__(self, key, getter):
- self._getters[key] = getter
-
- def __delitem__(self, key):
- del self._values[key]
- del self._getters[key]
-
- def __len__(self):
- return len(self._getters)
-
- def __iter__(self):
- return iter(self._getters.keys())
-
- def __repr__(self):
- rv = dict(self._values)
- for key in self._getters.keys():
- if key not in rv:
- rv[key] = 'unknown'
- return str(rv)
-
-
class Packages(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.StructureValidator,
Bcfg2.Server.Plugin.Generator,
@@ -103,7 +57,8 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
help="Packages backends to load",
type=Bcfg2.Options.Types.comma_list,
action=PackagesBackendAction,
- default=['Yum', 'Apt', 'Pac', 'Pkgng']),
+ default=['Yum', 'Apt', 'Pac', 'Pkgng', 'Portage', 'Layman',
+ 'Dummy']),
Bcfg2.Options.PathOption(
cf=("packages", "cache"), dest="packages_cache",
help="Path to the Packages cache",
@@ -361,6 +316,10 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
groups = []
recommended = dict()
+ pinned_src = dict()
+ if hasattr(metadata, 'PkgVars'):
+ pinned_src = metadata.PkgVars['pin']
+
for struct in structures:
for pkg in struct.xpath('//Package | //BoundPackage'):
if pkg.get("name"):
@@ -402,11 +361,11 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
base.update(collection.get_essential())
# check for this set of packages in the package cache
- pkey = hash(tuple(base))
+ pkey = hash((tuple(base), tuple(recommended), tuple(pinned_src)))
pcache = Bcfg2.Server.Cache.Cache("Packages", "pkg_sets",
collection.cachekey)
if pkey not in pcache:
- pcache[pkey] = collection.complete(base, recommended)
+ pcache[pkey] = collection.complete(base, recommended, pinned_src)
packages, unknown = pcache[pkey]
if unknown:
self.logger.info("Packages: Got %d unknown entries" % len(unknown))
@@ -548,21 +507,23 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
for source in self.sources.entries:
if source.applies(metadata):
relevant.append(source)
- sclasses.update([source.__class__])
+ if 'cclass' in dir(source):
+ sclasses.update([source.cclass])
+ else:
+ sclass = source.__class__.__name__.replace("Source", "")
+ sclasses.update([sclass])
if len(sclasses) > 1:
self.logger.warning("Packages: Multiple source types found for "
"%s: %s" %
- (metadata.hostname,
- ",".join([s.__name__ for s in sclasses])))
+ (metadata.hostname, ",".join([sclasses])))
cclass = Collection
elif len(sclasses) == 0:
self.logger.error("Packages: No sources found for %s" %
metadata.hostname)
cclass = Collection
else:
- cclass = get_collection_class(
- sclasses.pop().__name__.replace("Source", ""))
+ cclass = get_collection_class(sclasses.pop())
if self.debug_flag:
self.logger.error("Packages: Using %s for Collection of sources "
@@ -578,7 +539,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
def get_additional_data(self, metadata):
""" Return additional data for the given client. This will be
- an :class:`Bcfg2.Server.Plugins.Packages.OnDemandDict`
+ an :class:`Bcfg2.Server.Plugin.OnDemandDict`
containing two keys:
* ``sources``, whose value is a list of data returned from
@@ -610,7 +571,7 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
get_collection() until it's absolutely necessary. """
return self.get_collection(metadata).get_additional_data()
- return OnDemandDict(
+ return Bcfg2.Server.Plugin.OnDemandDict(
sources=get_sources,
get_config=lambda: self.get_config)
diff --git a/src/lib/Bcfg2/Server/Plugins/PkgVars.py b/src/lib/Bcfg2/Server/Plugins/PkgVars.py
new file mode 100644
index 000000000..9a2649d02
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/PkgVars.py
@@ -0,0 +1,65 @@
+import os
+import re
+import sys
+import copy
+import logging
+import lxml.etree
+import Bcfg2.Server.Plugin
+
+logger = logging.getLogger('Bcfg2.Plugins.PkgVars')
+vars = ['pin', 'use', 'keywords']
+
+class PkgVarsFile(Bcfg2.Server.Plugin.StructFile):
+ def get_additional_data(self, meta):
+ data = self.Match(meta)
+ results = {}
+ for d in data:
+ name = d.get('name', '')
+
+ for v in vars:
+ value = d.get(v, None)
+ if value:
+ if v not in results:
+ results[v] = {}
+ if name not in results[v]:
+ results[v][name] = set()
+
+ results[v][name].add(value)
+
+ return results
+
+class PkgVarsDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
+ __child__ = PkgVarsFile
+ patterns = re.compile(r'.*\.xml$')
+
+ def get_additional_data(self, meta):
+ results = {}
+ for v in vars:
+ results[v] = {}
+
+ for files in self.entries:
+ new = self.entries[files].get_additional_data(meta)
+ for x in vars:
+ if x in new:
+ results[x].update(new[x])
+
+ return results
+
+class PkgVars(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector):
+ name = 'PkgVars'
+ version = '$Revision$'
+
+ def __init__(self, core):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ try:
+ self.store = PkgVarsDirectoryBacked(self.data)
+ except OSError:
+ e = sys.exc_info()[1]
+ self.logger.error("Error while creating PkgVars store: %s %s" %
+ (e.strerror, e.filename))
+ raise Bcfg2.Server.Plugin.PluginInitError
+
+ def get_additional_data(self, meta):
+ return self.store.get_additional_data(meta)
diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py
index 76aab69b5..0a0ed9414 100644
--- a/src/lib/Bcfg2/Server/Plugins/Probes.py
+++ b/src/lib/Bcfg2/Server/Plugins/Probes.py
@@ -15,7 +15,12 @@ import Bcfg2.Server.FileMonitor
from Bcfg2.Logger import Debuggable
from Bcfg2.Server.Statistics import track_statistics
-HAS_DJANGO = False
+try:
+ from django.db import models
+ HAS_DJANGO = True
+except ImportError:
+ HAS_DJANGO = False
+
# pylint: disable=C0103
ProbesDataModel = None
ProbesGroupsModel = None
@@ -25,13 +30,10 @@ ProbesGroupsModel = None
def load_django_models():
""" Load models for Django after option parsing has completed """
# pylint: disable=W0602
- global ProbesDataModel, ProbesGroupsModel, HAS_DJANGO
+ global ProbesDataModel, ProbesGroupsModel
# pylint: enable=W0602
- try:
- from django.db import models
- HAS_DJANGO = True
- except ImportError:
- HAS_DJANGO = False
+
+ if not HAS_DJANGO:
return
class ProbesDataModel(models.Model, # pylint: disable=W0621,W0612
@@ -74,6 +76,7 @@ class ProbeStore(Debuggable):
def __init__(self, core, datadir): # pylint: disable=W0613
Debuggable.__init__(self)
+ self.core = core
self._groupcache = Bcfg2.Server.Cache.Cache("Probes", "probegroups")
self._datacache = Bcfg2.Server.Cache.Cache("Probes", "probedata")
@@ -134,7 +137,7 @@ class DBProbeStore(ProbeStore, Bcfg2.Server.Plugin.DatabaseBacked):
Bcfg2.Server.Cache.expire("Probes", "probegroups", hostname)
groupdata = ProbesGroupsModel.objects.filter(hostname=hostname)
self._groupcache[hostname] = list(set(r.group for r in groupdata))
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
@Bcfg2.Server.Plugin.DatabaseBacked.get_db_lock
def set_groups(self, hostname, groups):
@@ -155,7 +158,7 @@ class DBProbeStore(ProbeStore, Bcfg2.Server.Plugin.DatabaseBacked):
ProbesGroupsModel.objects.filter(
hostname=hostname).exclude(group__in=groups).delete()
if olddata != groups:
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
def _load_data(self, hostname):
Bcfg2.Server.Cache.expire("Probes", "probegroups", hostname)
@@ -168,7 +171,7 @@ class DBProbeStore(ProbeStore, Bcfg2.Server.Plugin.DatabaseBacked):
time.mktime(pdata.timestamp.timetuple())
ts_set = True
self._datacache[hostname][pdata.probe] = ProbeData(pdata.data)
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
@Bcfg2.Server.Plugin.DatabaseBacked.get_db_lock
def set_data(self, hostname, data):
@@ -198,7 +201,7 @@ class DBProbeStore(ProbeStore, Bcfg2.Server.Plugin.DatabaseBacked):
qset.delete()
expire_metadata = True
if expire_metadata:
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
class XMLProbeStore(ProbeStore):
@@ -234,7 +237,7 @@ class XMLProbeStore(ProbeStore):
self._groupcache[client.get('name')].append(
pdata.get('name'))
- Bcfg2.Server.Cache.expire("Metadata")
+ self.core.metadata_cache.expire()
def _load_groups(self, hostname):
self._load_data(hostname)
@@ -274,7 +277,7 @@ class XMLProbeStore(ProbeStore):
olddata = self._groupcache.get(hostname, [])
self._groupcache[hostname] = groups
if olddata != groups:
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
def set_data(self, hostname, data):
Bcfg2.Server.Cache.expire("Probes", "probedata", hostname)
@@ -285,7 +288,7 @@ class XMLProbeStore(ProbeStore):
self._datacache[hostname][probe] = pdata
expire_metadata |= olddata != data
if expire_metadata:
- Bcfg2.Server.Cache.expire("Metadata", hostname)
+ self.core.metadata_cache.expire(hostname)
class ClientProbeDataSet(dict):
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index c4dd75e60..e6549b714 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -35,13 +35,17 @@ LOGGER = logging.getLogger(__name__)
class PropertyFile(object):
""" Base Properties file handler """
- def __init__(self, name):
+ def __init__(self, name, core):
"""
:param name: The filename of this properties file.
+ :type name: string
+ :param core: The Bcfg2.Server.Core initializing the Properties plugin
+ :type core: Bcfg2.Server.Core
.. automethod:: _write
"""
self.name = name
+ self.core = core
def write(self):
""" Write the data in this data structure back to the property
@@ -69,6 +73,12 @@ class PropertyFile(object):
file. """
raise NotImplementedError
+ def _expire_metadata_cache(self):
+ """ Expires the metadata cache, if it is required by the caching
+ mode. """
+ if self.core.metadata_cache_mode in ['cautious', 'aggressive']:
+ self.core.metadata_cache.expire()
+
def validate_data(self):
""" Verify that the data in this file is valid. """
raise NotImplementedError
@@ -81,9 +91,9 @@ class PropertyFile(object):
class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
"""Handle JSON Properties files."""
- def __init__(self, name):
+ def __init__(self, name, core):
Bcfg2.Server.Plugin.FileBacked.__init__(self, name)
- PropertyFile.__init__(self, name)
+ PropertyFile.__init__(self, name, core)
self.json = None
def Index(self):
@@ -93,10 +103,13 @@ class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
err = sys.exc_info()[1]
raise PluginExecutionError("Could not load JSON data from %s: %s" %
(self.name, err))
+ self._expire_metadata_cache()
+ Index.__doc__ = Bcfg2.Server.Plugin.FileBacked.Index.__doc__
def _write(self):
json.dump(self.json, open(self.name, 'wb'))
return True
+ _write.__doc__ = PropertyFile._write.__doc__
def validate_data(self):
try:
@@ -105,6 +118,7 @@ class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
err = sys.exc_info()[1]
raise PluginExecutionError("Data for %s cannot be dumped to JSON: "
"%s" % (self.name, err))
+ validate_data.__doc__ = PropertyFile.validate_data.__doc__
def __str__(self):
return str(self.json)
@@ -116,11 +130,10 @@ class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
class YAMLPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
""" Handle YAML Properties files. """
- def __init__(self, name):
+ def __init__(self, name, core):
Bcfg2.Server.Plugin.FileBacked.__init__(self, name)
- PropertyFile.__init__(self, name)
+ PropertyFile.__init__(self, name, core)
self.yaml = None
- __init__.__doc__ = Bcfg2.Server.Plugin.FileBacked.__init__.__doc__
def Index(self):
try:
@@ -129,6 +142,7 @@ class YAMLPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
err = sys.exc_info()[1]
raise PluginExecutionError("Could not load YAML data from %s: %s" %
(self.name, err))
+ self._expire_metadata_cache()
Index.__doc__ = Bcfg2.Server.Plugin.FileBacked.Index.__doc__
def _write(self):
@@ -155,10 +169,15 @@ class YAMLPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
class XMLPropertyFile(Bcfg2.Server.Plugin.StructFile, PropertyFile):
""" Handle XML Properties files. """
- def __init__(self, name, should_monitor=False):
+ def __init__(self, name, core, should_monitor=False):
Bcfg2.Server.Plugin.StructFile.__init__(self, name,
should_monitor=should_monitor)
- PropertyFile.__init__(self, name)
+ PropertyFile.__init__(self, name, core)
+
+ def Index(self):
+ Bcfg2.Server.Plugin.StructFile.Index(self)
+ self._expire_metadata_cache()
+ Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__
def _write(self):
open(self.name, "wb").write(
@@ -258,11 +277,11 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
:class:`PropertyFile`
"""
if fname.endswith(".xml"):
- return XMLPropertyFile(fname)
+ return XMLPropertyFile(fname, self.core)
elif HAS_JSON and fname.endswith(".json"):
- return JSONPropertyFile(fname)
+ return JSONPropertyFile(fname, self.core)
elif HAS_YAML and (fname.endswith(".yaml") or fname.endswith(".yml")):
- return YAMLPropertyFile(fname)
+ return YAMLPropertyFile(fname, self.core)
else:
raise Bcfg2.Server.Plugin.PluginExecutionError(
"Properties: Unknown extension %s" % fname)
diff --git a/src/lib/Bcfg2/Server/Plugins/PuppetENC.py b/src/lib/Bcfg2/Server/Plugins/PuppetENC.py
index 59fbe6f03..e2d8a058f 100644
--- a/src/lib/Bcfg2/Server/Plugins/PuppetENC.py
+++ b/src/lib/Bcfg2/Server/Plugins/PuppetENC.py
@@ -117,7 +117,7 @@ class PuppetENC(Bcfg2.Server.Plugin.Plugin,
self.logger.warning("PuppetENC is incompatible with aggressive "
"client metadata caching, try 'cautious' or "
"'initial' instead")
- self.core.expire_caches_by_type(Bcfg2.Server.Plugin.Metadata)
+ self.core.metadata_cache.expire()
def end_statistics(self, metadata):
self.end_client_run(self, metadata)
diff --git a/src/lib/Bcfg2/Server/Plugins/Reporting.py b/src/lib/Bcfg2/Server/Plugins/Reporting.py
index 5c73546b4..e372006c7 100644
--- a/src/lib/Bcfg2/Server/Plugins/Reporting.py
+++ b/src/lib/Bcfg2/Server/Plugins/Reporting.py
@@ -9,12 +9,15 @@ from Bcfg2.Reporting.Transport.base import TransportError
from Bcfg2.Server.Plugin import Statistics, PullSource, Threaded, \
PluginInitError, PluginExecutionError
-# required for reporting
try:
- import south # pylint: disable=W0611
- HAS_SOUTH = True
+ import django
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ HAS_REPORTING = True
+ else:
+ import south # pylint: disable=W0611
+ HAS_REPORTING = True
except ImportError:
- HAS_SOUTH = False
+ HAS_REPORTING = False
def _rpc_call(method):
@@ -48,8 +51,8 @@ class Reporting(Statistics, Threaded, PullSource):
self.whoami = platform.node()
self.transport = None
- if not HAS_SOUTH:
- msg = "Django south is required for Reporting"
+ if not HAS_REPORTING:
+ msg = "Django 1.7+ or Django south is required for Reporting"
self.logger.error(msg)
raise PluginInitError(msg)
diff --git a/src/lib/Bcfg2/Server/Plugins/Rules.py b/src/lib/Bcfg2/Server/Plugins/Rules.py
index a3f682ed6..cf659251c 100644
--- a/src/lib/Bcfg2/Server/Plugins/Rules.py
+++ b/src/lib/Bcfg2/Server/Plugins/Rules.py
@@ -1,10 +1,17 @@
"""This generator provides rule-based entry mappings."""
+import copy
import re
+import string
import Bcfg2.Options
import Bcfg2.Server.Plugin
+class NameTemplate(string.Template):
+ """Simple subclass of string.Template with a custom delimiter."""
+ delimiter = '%'
+
+
class Rules(Bcfg2.Server.Plugin.PrioDir):
"""This is a generator that handles service assignments."""
__author__ = 'bcfg-dev@mcs.anl.gov'
@@ -12,7 +19,10 @@ class Rules(Bcfg2.Server.Plugin.PrioDir):
options = Bcfg2.Server.Plugin.PrioDir.options + [
Bcfg2.Options.BooleanOption(
cf=("rules", "regex"), dest="rules_regex",
- help="Allow regular expressions in Rules")]
+ help="Allow regular expressions in Rules"),
+ Bcfg2.Options.BooleanOption(
+ cf=("rules", "replace_name"), dest="rules_replace_name",
+ help="Replace %{name} in attributes with name of target entry")]
def __init__(self, core):
Bcfg2.Server.Plugin.PrioDir.__init__(self, core)
@@ -46,7 +56,22 @@ class Rules(Bcfg2.Server.Plugin.PrioDir):
return True
return False
+ def _apply(self, entry, data):
+ if self._replace_name_enabled:
+ data = copy.deepcopy(data)
+ for key, val in list(data.attrib.items()):
+ data.attrib[key] = NameTemplate(val).safe_substitute(
+ name=entry.get('name'))
+
+ Bcfg2.Server.Plugin.PrioDir._apply(self, entry, data)
+
@property
def _regex_enabled(self):
""" Return True if rules regexes are enabled, False otherwise """
return Bcfg2.Options.setup.rules_regex
+
+ @property
+ def _replace_name_enabled(self):
+ """ Return True if the replace_name feature is enabled,
+ False otherwise """
+ return Bcfg2.Options.setup.rules_replace_name
diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
index e4fb9b565..08acc4d8d 100644
--- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py
+++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
@@ -103,6 +103,7 @@ class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet):
class SSHbase(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector,
Bcfg2.Server.Plugin.Generator,
Bcfg2.Server.Plugin.PullTarget):
"""
@@ -120,6 +121,10 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
private key for (hostname)
ssh_host_(ec)(dr)sa_key.pub.H_(hostname) -> the v2 ssh host
public key for (hostname)
+ ssh_host_ed25519_key.H_(hostname) -> the v2 ssh host
+ private key for (hostname)
+ ssh_host_ed25519_key.pub.H_(hostname) -> the v2 ssh host
+ public key for (hostname)
ssh_known_hosts -> the current known hosts file. this
is regenerated each time a new key is generated.
@@ -127,10 +132,12 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
__author__ = 'bcfg-dev@mcs.anl.gov'
keypatterns = ["ssh_host_dsa_key",
"ssh_host_ecdsa_key",
+ "ssh_host_ed25519_key",
"ssh_host_rsa_key",
"ssh_host_key",
"ssh_host_dsa_key.pub",
"ssh_host_ecdsa_key.pub",
+ "ssh_host_ed25519_key.pub",
"ssh_host_rsa_key.pub",
"ssh_host_key.pub"]
@@ -141,6 +148,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
def __init__(self, core):
Bcfg2.Server.Plugin.Plugin.__init__(self, core)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
Bcfg2.Server.Plugin.Generator.__init__(self)
Bcfg2.Server.Plugin.PullTarget.__init__(self)
self.ipcache = {}
@@ -210,7 +218,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
try:
names[cmeta.hostname].update(
self.get_namecache_entry(ip))
- except socket.gaierror:
+ except socket.herror:
continue
names[cmeta.hostname] = sorted(names[cmeta.hostname])
@@ -284,6 +292,10 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.debug_log("New public key %s; invalidating "
"ssh_known_hosts cache" % event.filename)
self.skn = False
+
+ if self.core.metadata_cache_mode in ['cautious',
+ 'aggressive']:
+ self.core.metadata_cache.expire()
return
if event.filename == 'info.xml':
@@ -332,7 +344,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.ipcache[client] = False
msg = "Failed to find IP address for %s: %s" % (client,
result.error)
- self.logger(msg)
+ self.logger.error(msg)
raise PluginExecutionError(msg)
def get_namecache_entry(self, cip):
@@ -342,7 +354,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
if self.namecache[cip]:
return self.namecache[cip]
else:
- raise socket.gaierror
+ raise socket.herror
else:
# add an entry that has not been cached
try:
@@ -353,7 +365,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.namecache[cip] = []
self.namecache[cip].extend(rvlookup[1])
return self.namecache[cip]
- except socket.gaierror:
+ except socket.herror:
self.namecache[cip] = False
self.logger.error("Failed to find any names associated with "
"IP address %s" % cip)
@@ -415,7 +427,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
def GenerateHostKeyPair(self, client, filename):
"""Generate new host key pair for client."""
- match = re.search(r'(ssh_host_(?:((?:ecd|d|r)sa)_)?key)', filename)
+ match = re.search(r'(ssh_host_(?:((?:ecd|d|r)sa|ed25519)_)?key)',
+ filename)
if match:
hostkey = "%s.H_%s" % (match.group(1), client)
if match.group(2):
@@ -489,3 +502,15 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.logger.error("Failed to pull %s. This file does not "
"currently exist on the client" %
entry.get('name'))
+
+ def get_additional_data(self, metadata):
+ data = dict()
+ for key in self.keypatterns:
+ if key.endswith(".pub"):
+ try:
+ keyfile = "/etc/ssh/" + key
+ entry = self.entries[keyfile].best_matching(metadata)
+ data[key] = entry.data
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ pass
+ return data
diff --git a/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py
index cec2de297..ff67571fa 100644
--- a/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py
+++ b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py
@@ -7,24 +7,18 @@ import lxml.etree
from Bcfg2.Server.Plugin import Plugin, Connector, DirectoryBacked, \
TemplateDataProvider, DefaultTemplateDataProvider
from Bcfg2.Logger import Debuggable
+from Bcfg2.Utils import safe_module_name
MODULE_RE = re.compile(r'(?P<filename>(?P<module>[^\/]+)\.py)$')
-def safe_module_name(module):
- """ Munge the name of a TemplateHelper module to avoid collisions
- with other Python modules. E.g., if someone has a helper named
- 'ldap.py', it should not be added to ``sys.modules`` as ``ldap``,
- but rather as something more obscure. """
- return '__TemplateHelper_%s' % module
-
-
class HelperModule(Debuggable):
""" Representation of a TemplateHelper module """
- def __init__(self, name):
+ def __init__(self, name, core):
Debuggable.__init__(self)
self.name = name
+ self.core = core
#: The name of the module as used by get_additional_data().
#: the name of the file with .py stripped off.
@@ -51,9 +45,14 @@ class HelperModule(Debuggable):
if event and event.code2str() not in ['exists', 'changed', 'created']:
return
+ # expire the metadata cache, because the module might have changed
+ if self.core.metadata_cache_mode in ['cautious', 'aggressive']:
+ self.core.metadata_cache.expire()
+
try:
- module = imp.load_source(safe_module_name(self._module_name),
- self.name)
+ module = imp.load_source(
+ safe_module_name('TemplateHelper', self._module_name),
+ self.name)
except: # pylint: disable=W0702
# this needs to be a blanket except because the
# imp.load_source() call can raise literally any error,
@@ -107,7 +106,6 @@ class TemplateHelper(Plugin, Connector, DirectoryBacked, TemplateDataProvider):
__author__ = 'chris.a.st.pierre@gmail.com'
ignore = re.compile(r'^(\.#.*|.*~|\..*\.(sw[px])|.*\.py[co])$')
patterns = MODULE_RE
- __child__ = HelperModule
def __init__(self, core):
Plugin.__init__(self, core)
@@ -115,6 +113,10 @@ class TemplateHelper(Plugin, Connector, DirectoryBacked, TemplateDataProvider):
DirectoryBacked.__init__(self, self.data)
TemplateDataProvider.__init__(self)
+ # The HelperModule needs access to the core, so we have to construct
+ # it manually and add the custom argument.
+ self.__child__ = lambda fname: HelperModule(fname, core)
+
def get_additional_data(self, _):
return dict([(h._module_name, h) # pylint: disable=W0212
for h in self.entries.values()])
diff --git a/src/lib/Bcfg2/Server/Reports/reports/models.py b/src/lib/Bcfg2/Server/Reports/reports/models.py
index ac4c8eac4..67aa425d9 100644
--- a/src/lib/Bcfg2/Server/Reports/reports/models.py
+++ b/src/lib/Bcfg2/Server/Reports/reports/models.py
@@ -266,7 +266,7 @@ class Reason(models.Model):
current_to = models.CharField(max_length=1024, blank=True)
version = models.CharField(max_length=1024, blank=True)
current_version = models.CharField(max_length=1024, blank=True)
- current_exists = models.BooleanField() # False means its missing. Default True
+ current_exists = models.BooleanField(default=True) # False means its missing.
current_diff = models.TextField(max_length=1024*1024, blank=True)
is_binary = models.BooleanField(default=False)
is_sensitive = models.BooleanField(default=False)
diff --git a/src/lib/Bcfg2/Server/Reports/updatefix.py b/src/lib/Bcfg2/Server/Reports/updatefix.py
index 91c370994..09b218464 100644
--- a/src/lib/Bcfg2/Server/Reports/updatefix.py
+++ b/src/lib/Bcfg2/Server/Reports/updatefix.py
@@ -4,7 +4,7 @@ import django.core.management
import sys
import logging
import traceback
-from Bcfg2.Server.models import InternalDatabaseVersion
+from Bcfg2.Server.models import internal_database_version
logger = logging.getLogger('Bcfg2.Server.Reports.UpdateFix')
@@ -138,7 +138,7 @@ def rollupdate(current_version):
exc_info=1)
# since array start at 0 but version start at 1
# we add 1 to the normal count
- ret = InternalDatabaseVersion.objects.create(version=i + 1)
+ ret = internal_database_version().create(version=i + 1)
return ret
else:
return None
@@ -149,10 +149,10 @@ def update_database():
try:
logger.debug("Running upgrade of models to the new one")
django.core.management.call_command("syncdb", interactive=False, verbosity=0)
- know_version = InternalDatabaseVersion.objects.order_by('-version')
+ know_version = internal_database_version().order_by('-version')
if not know_version:
logger.debug("No version, creating initial version")
- know_version = InternalDatabaseVersion.objects.create(version=lastversion)
+ know_version = internal_database_version().create(version=lastversion)
else:
know_version = know_version[0]
logger.debug("Presently at %s" % know_version)
diff --git a/src/lib/Bcfg2/Server/migrations/0001_initial.py b/src/lib/Bcfg2/Server/migrations/0001_initial.py
new file mode 100644
index 000000000..3b3dca455
--- /dev/null
+++ b/src/lib/Bcfg2/Server/migrations/0001_initial.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.9 on 2016-08-17 18:52
+from __future__ import unicode_literals
+
+import Bcfg2.Server.Plugin.helpers
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MetadataClientModel',
+ fields=[
+ ('hostname', models.CharField(max_length=255, primary_key=True, serialize=False)),
+ ('version', models.CharField(max_length=31, null=True)),
+ ],
+ bases=(models.Model, Bcfg2.Server.Plugin.helpers.PluginDatabaseModel),
+ ),
+ migrations.CreateModel(
+ name='ProbesDataModel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('hostname', models.CharField(max_length=255)),
+ ('probe', models.CharField(max_length=255)),
+ ('timestamp', models.DateTimeField(auto_now=True)),
+ ('data', models.TextField(null=True)),
+ ],
+ bases=(models.Model, Bcfg2.Server.Plugin.helpers.PluginDatabaseModel),
+ ),
+ migrations.CreateModel(
+ name='ProbesGroupsModel',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('hostname', models.CharField(max_length=255)),
+ ('group', models.CharField(max_length=255)),
+ ],
+ bases=(models.Model, Bcfg2.Server.Plugin.helpers.PluginDatabaseModel),
+ ),
+ ]
diff --git a/src/lib/Bcfg2/Server/migrations/__init__.py b/src/lib/Bcfg2/Server/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/lib/Bcfg2/Server/migrations/__init__.py
diff --git a/src/lib/Bcfg2/Server/models.py b/src/lib/Bcfg2/Server/models.py
index 8d6642a25..9c0153c74 100644
--- a/src/lib/Bcfg2/Server/models.py
+++ b/src/lib/Bcfg2/Server/models.py
@@ -8,6 +8,7 @@ import Bcfg2.Server.Plugins
LOGGER = logging.getLogger(__name__)
MODELS = []
+INTERNAL_DATABASE_VERSION = None
class _OptionContainer(object):
@@ -56,15 +57,23 @@ def load_models(plugins=None):
setattr(sys.modules[__name__], sym, obj)
MODELS.append(sym)
- class InternalDatabaseVersion(models.Model):
- """ Object that tell us to which version the database is """
- version = models.IntegerField()
- updated = models.DateTimeField(auto_now_add=True)
+def internal_database_version():
+ global INTERNAL_DATABASE_VERSION
- def __str__(self):
- return "version %d updated %s" % (self.version,
- self.updated.isoformat())
+ if INTERNAL_DATABASE_VERSION is None:
+ from django.db import models
+ class InternalDatabaseVersion(models.Model):
+ """ Object that tell us to which version the database is """
+ version = models.IntegerField()
+ updated = models.DateTimeField(auto_now_add=True)
- class Meta: # pylint: disable=C0111,W0232
- app_label = "reports"
- get_latest_by = "version"
+ def __str__(self):
+ return "version %d updated %s" % (self.version,
+ self.updated.isoformat())
+
+ class Meta: # pylint: disable=C0111,W0232
+ app_label = "reports"
+ get_latest_by = "version"
+ INTERNAL_DATABASE_VERSION = InternalDatabaseVersion
+
+ return INTERNAL_DATABASE_VERSION.objects
diff --git a/src/lib/Bcfg2/Server/south_migrations/0001_initial.py b/src/lib/Bcfg2/Server/south_migrations/0001_initial.py
new file mode 100644
index 000000000..864c311e5
--- /dev/null
+++ b/src/lib/Bcfg2/Server/south_migrations/0001_initial.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'MetadataClientModel'
+ db.create_table(u'Server_metadataclientmodel', (
+ ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255, primary_key=True)),
+ ('version', self.gf('django.db.models.fields.CharField')(max_length=31, null=True)),
+ ))
+ db.send_create_signal('Server', ['MetadataClientModel'])
+
+ # Adding model 'ProbesDataModel'
+ db.create_table(u'Server_probesdatamodel', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('probe', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ('data', self.gf('django.db.models.fields.TextField')(null=True)),
+ ))
+ db.send_create_signal('Server', ['ProbesDataModel'])
+
+ # Adding model 'ProbesGroupsModel'
+ db.create_table(u'Server_probesgroupsmodel', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('group', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ))
+ db.send_create_signal('Server', ['ProbesGroupsModel'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'MetadataClientModel'
+ db.delete_table(u'Server_metadataclientmodel')
+
+ # Deleting model 'ProbesDataModel'
+ db.delete_table(u'Server_probesdatamodel')
+
+ # Deleting model 'ProbesGroupsModel'
+ db.delete_table(u'Server_probesgroupsmodel')
+
+
+ models = {
+ 'Server.metadataclientmodel': {
+ 'Meta': {'object_name': 'MetadataClientModel'},
+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '31', 'null': 'True'})
+ },
+ 'Server.probesdatamodel': {
+ 'Meta': {'object_name': 'ProbesDataModel'},
+ 'data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'probe': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ 'Server.probesgroupsmodel': {
+ 'Meta': {'object_name': 'ProbesGroupsModel'},
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ }
+ }
+
+ complete_apps = ['Server'] \ No newline at end of file
diff --git a/src/lib/Bcfg2/Server/south_migrations/__init__.py b/src/lib/Bcfg2/Server/south_migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/lib/Bcfg2/Server/south_migrations/__init__.py
diff --git a/src/lib/Bcfg2/Utils.py b/src/lib/Bcfg2/Utils.py
index 10057b63e..b043fd11c 100644
--- a/src/lib/Bcfg2/Utils.py
+++ b/src/lib/Bcfg2/Utils.py
@@ -321,6 +321,15 @@ def safe_input(msg):
return input(msg)
+def safe_module_name(prefix, module):
+ """ Munge the name of a module with prefix to avoid collisions
+ with other Python modules. E.g., if you want to import user
+ defined helper modules and someone has a helper named 'ldap.py',
+ it should not be added to ``sys.modules`` as ``ldap``, but rather
+ as something more obscure. """
+ return '__%s_%s' % (prefix, module)
+
+
class classproperty(object): # pylint: disable=C0103
""" Decorator that can be used to create read-only class
properties. """
@@ -329,4 +338,20 @@ class classproperty(object): # pylint: disable=C0103
self.getter = getter
def __get__(self, instance, owner):
- return self.getter(owner)
+ return classmethod(self.getter).__get__(None, owner)()
+
+
+def is_string(strng, encoding):
+ """ Returns true if the string contains no ASCII control
+ characters and can be decoded from the specified encoding. """
+ for char in strng:
+ if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
+ return False
+ if not hasattr(strng, "decode"):
+ # py3k
+ return True
+ try:
+ strng.decode(encoding)
+ return True
+ except: # pylint: disable=W0702
+ return False
diff --git a/src/lib/Bcfg2/manage.py b/src/lib/Bcfg2/manage.py
index 3e4eedc9f..9675a3db1 100755
--- a/src/lib/Bcfg2/manage.py
+++ b/src/lib/Bcfg2/manage.py
@@ -1,14 +1,31 @@
#!/usr/bin/env python
-from django.core.management import execute_manager
-import imp
+""" Wrapper for the django manage.py with the Bcfg2 Opitons parsing. """
+
+import sys
+import django
+import Bcfg2.Options
+import Bcfg2.DBSettings
+
try:
- imp.find_module('settings') # Assumed to be in the same directory.
+ import Bcfg2.Server.models
except ImportError:
- import sys
- sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
- sys.exit(1)
+ pass
+
+
+def main():
+ parser = Bcfg2.Options.get_parser()
+ parser.add_options([
+ Bcfg2.Options.PositionalArgument('django_command', nargs='*')])
+ parser.parse()
+
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 6:
+ from django.core.management import execute_from_command_line
+ execute_from_command_line(
+ sys.argv[:1] + Bcfg2.Options.setup.django_command)
+ else:
+ from django.core.management import execute_manager
+ execute_manager(Bcfg2.DBSettings.settings)
-import settings
if __name__ == "__main__":
- execute_manager(settings)
+ main()
diff --git a/src/lib/Bcfg2/version.py b/src/lib/Bcfg2/version.py
index 196d77273..ab5439528 100644
--- a/src/lib/Bcfg2/version.py
+++ b/src/lib/Bcfg2/version.py
@@ -2,7 +2,7 @@
import re
-__version__ = "1.4.0pre1"
+__version__ = "1.4.0pre2"
class Bcfg2VersionInfo(tuple): # pylint: disable=E0012,R0924
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index adfa96852..3d9b0d87b 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -4,5 +4,6 @@
import sys
from Bcfg2.Server.Info import CLI
+
if __name__ == '__main__':
sys.exit(CLI().run())
diff --git a/testsuite/Testschema/test_schema.py b/testsuite/Testschema/test_schema.py
index cd9b74cdf..138468a91 100644
--- a/testsuite/Testschema/test_schema.py
+++ b/testsuite/Testschema/test_schema.py
@@ -34,13 +34,18 @@ NSMAP = dict(xs=XS)
class TestSchemas(Bcfg2TestCase):
schema_url = "http://www.w3.org/2001/XMLSchema.xsd"
+ catalog_file = os.path.expanduser("~/.cache/xml/catalog.xml")
@skipUnless(HAS_XMLLINT, "xmllint not installed")
def test_valid(self):
+ env = os.environ.copy()
+ if os.path.exists(self.catalog_file):
+ print('Using cached schema files.')
+ env["SGML_CATALOG_FILES"] = self.catalog_file
schemas = [s for s in glob.glob(os.path.join(srcpath, '*.xsd'))]
- xmllint = Popen(['xmllint', '--xinclude', '--noout', '--schema',
- self.schema_url] + schemas,
- stdout=PIPE, stderr=STDOUT)
+ xmllint = Popen(['xmllint', '--xinclude', '--noout', '--catalogs',
+ '--schema', self.schema_url] + schemas,
+ stdout=PIPE, stderr=STDOUT, env=env)
print(xmllint.communicate()[0].decode())
self.assertEqual(xmllint.wait(), 0)
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
index 69dd562be..47d3b84ed 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
@@ -37,22 +37,6 @@ class TestPOSIXFile(TestPOSIXTool):
entry.text = "text"
self.assertTrue(ptool.fully_specified(entry))
- def test_is_string(self):
- ptool = self.get_obj()
-
- for char in list(range(8)) + list(range(14, 32)):
- self.assertFalse(ptool._is_string("foo" + chr(char) + "bar",
- 'UTF-8'))
- for char in list(range(9, 14)) + list(range(33, 128)):
- self.assertTrue(ptool._is_string("foo" + chr(char) + "bar",
- 'UTF-8'))
- ustr = 'é'
- self.assertTrue(ptool._is_string(ustr, 'UTF-8'))
- if not inPy3k:
- self.assertFalse(ptool._is_string("foo" + chr(128) + "bar",
- 'ascii'))
- self.assertFalse(ptool._is_string(ustr, 'ascii'))
-
def test_get_data(self):
orig_entry = lxml.etree.Element("Path", name="/test", type="file")
Bcfg2.Options.setup.encoding = "ascii"
@@ -216,7 +200,8 @@ class TestPOSIXFile(TestPOSIXTool):
mock_unlink.assert_called_with(newfile)
@patch("%s.open" % builtins)
- def test__get_diffs(self, mock_open):
+ @patch("Bcfg2.Utils")
+ def test__get_diffs(self, mock_utils, mock_open):
orig_entry = lxml.etree.Element("Path", name="/test", type="file",
mode='0644', owner='root',
group='root')
@@ -226,16 +211,15 @@ class TestPOSIXFile(TestPOSIXTool):
ptool = self.get_obj()
ptool._get_data = Mock()
ptool._diff = Mock()
- ptool._is_string = Mock()
def reset():
- ptool._is_string.reset_mock()
+ mock_utils.is_string.reset_mock()
ptool._get_data.reset_mock()
ptool._diff.reset_mock()
mock_open.reset_mock()
return copy.deepcopy(orig_entry)
- ptool._is_string.return_value = True
+ mock_utils.is_string.return_value = True
ptool._get_data.return_value = (orig_entry.text, False)
mock_open.return_value.read.return_value = ondisk
ptool._diff.return_value = ["-test2", "+test"]
@@ -250,7 +234,7 @@ class TestPOSIXFile(TestPOSIXTool):
# binary data on disk
entry = reset()
- ptool._is_string.return_value = False
+ mock_utils.is_string.return_value = False
ptool._get_diffs(entry, content=ondisk)
self.assertFalse(mock_open.called)
self.assertFalse(ptool._diff.called)
@@ -258,7 +242,7 @@ class TestPOSIXFile(TestPOSIXTool):
# sensitive, non-interactive -- do nothing
entry = reset()
- ptool._is_string.return_value = True
+ mock_utils.is_string.return_value = True
ptool._get_diffs(entry, sensitive=True, interactive=False)
self.assertFalse(mock_open.called)
self.assertFalse(ptool._diff.called)
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
index ea4ca3f5f..b137b0f0c 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
@@ -194,11 +194,11 @@ class TestPOSIXTool(TestTool):
mock_lstat.assert_called_with(entry.get('name'))
ptool._remove.assert_called_with(entry)
- @patch("os.chown")
+ @patch("os.lchown")
@patch("os.chmod")
@patch("os.utime")
@patch("os.geteuid")
- def test_set_perms(self, mock_geteuid, mock_utime, mock_chmod, mock_chown):
+ def test_set_perms(self, mock_geteuid, mock_utime, mock_chmod, mock_lchown):
ptool = self.get_obj()
ptool._norm_entry_uid = Mock()
ptool._norm_entry_gid = Mock()
@@ -210,7 +210,7 @@ class TestPOSIXTool(TestTool):
ptool._norm_entry_gid.reset_mock()
ptool._norm_entry_uid.reset_mock()
mock_chmod.reset_mock()
- mock_chown.reset_mock()
+ mock_lchown.reset_mock()
mock_utime.reset_mock()
mock_geteuid.reset_mock()
@@ -235,7 +235,7 @@ class TestPOSIXTool(TestTool):
self.assertTrue(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with(entry.get("name"), 10, 100)
+ mock_lchown.assert_called_with(entry.get("name"), 10, 100)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8))
self.assertFalse(mock_utime.called)
@@ -250,7 +250,7 @@ class TestPOSIXTool(TestTool):
self.assertTrue(ptool._set_perms(entry))
self.assertFalse(ptool._norm_entry_uid.called)
self.assertFalse(ptool._norm_entry_gid.called)
- self.assertFalse(mock_chown.called)
+ self.assertFalse(mock_lchown.called)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8))
self.assertFalse(mock_utime.called)
@@ -265,7 +265,7 @@ class TestPOSIXTool(TestTool):
self.assertTrue(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with(entry.get("name"), 10, 100)
+ mock_lchown.assert_called_with(entry.get("name"), 10, 100)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8))
mock_utime.assert_called_with(entry.get("name"), (mtime, mtime))
@@ -276,26 +276,26 @@ class TestPOSIXTool(TestTool):
self.assertTrue(ptool._set_perms(entry, path='/etc/bar'))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with('/etc/bar', 10, 100)
+ mock_lchown.assert_called_with('/etc/bar', 10, 100)
mock_chmod.assert_called_with('/etc/bar', int(entry.get("mode"), 8))
mock_utime.assert_called_with(entry.get("name"), (mtime, mtime))
ptool._set_secontext.assert_called_with(entry, path='/etc/bar')
ptool._set_acls.assert_called_with(entry, path='/etc/bar')
- # test dev_type modification of perms, failure of chown
+ # test dev_type modification of perms, failure of lchown
reset()
def chown_rv(path, owner, group):
if owner == 0 and group == 0:
return True
else:
raise KeyError
- os.chown.side_effect = chown_rv
+ os.lchown.side_effect = chown_rv
entry.set("type", "device")
entry.set("dev_type", list(device_map.keys())[0])
self.assertFalse(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with(entry.get("name"), 0, 0)
+ mock_lchown.assert_called_with(entry.get("name"), 0, 0)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8) | list(device_map.values())[0])
mock_utime.assert_called_with(entry.get("name"), (mtime, mtime))
@@ -304,14 +304,14 @@ class TestPOSIXTool(TestTool):
# test failure of chmod
reset()
- os.chown.side_effect = None
+ os.lchown.side_effect = None
os.chmod.side_effect = OSError
entry.set("type", "file")
del entry.attrib["dev_type"]
self.assertFalse(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with(entry.get("name"), 10, 100)
+ mock_lchown.assert_called_with(entry.get("name"), 10, 100)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8))
mock_utime.assert_called_with(entry.get("name"), (mtime, mtime))
@@ -322,14 +322,14 @@ class TestPOSIXTool(TestTool):
# e.g., when chmod fails, we still try to apply acls, set
# selinux context, etc.
reset()
- os.chown.side_effect = OSError
+ os.lchown.side_effect = OSError
os.utime.side_effect = OSError
ptool._set_acls.return_value = False
ptool._set_secontext.return_value = False
self.assertFalse(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
- mock_chown.assert_called_with(entry.get("name"), 10, 100)
+ mock_lchown.assert_called_with(entry.get("name"), 10, 100)
mock_chmod.assert_called_with(entry.get("name"),
int(entry.get("mode"), 8))
mock_utime.assert_called_with(entry.get("name"), (mtime, mtime))
@@ -481,11 +481,16 @@ class TestPOSIXTool(TestTool):
@skipUnless(HAS_SELINUX, "SELinux not found, skipping")
@patchIf(HAS_SELINUX, "selinux.restorecon")
+ @patchIf(HAS_SELINUX, "selinux.lgetfilecon")
@patchIf(HAS_SELINUX, "selinux.lsetfilecon")
- def test_set_secontext(self, mock_lsetfilecon, mock_restorecon):
+ def test_set_secontext(self, mock_lsetfilecon, mock_lgetfilecon,
+ mock_restorecon):
+ Bcfg2.Options.setup.secontext_ignore = ['dosfs_t']
ptool = self.get_obj()
entry = lxml.etree.Element("Path", name="/etc/foo", type="file")
+ mock_lgetfilecon.return_value = (0, "system_u:object_r:foo_t")
+
# disable selinux for the initial test
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX = False
self.assertTrue(ptool._set_secontext(entry))
@@ -495,29 +500,46 @@ class TestPOSIXTool(TestTool):
self.assertTrue(ptool._set_secontext(entry))
self.assertFalse(mock_restorecon.called)
self.assertFalse(mock_lsetfilecon.called)
+ self.assertFalse(mock_lgetfilecon.called)
mock_restorecon.reset_mock()
mock_lsetfilecon.reset_mock()
+ mock_lgetfilecon.reset_mock()
entry.set("secontext", "__default__")
self.assertTrue(ptool._set_secontext(entry))
mock_restorecon.assert_called_with(entry.get("name"))
+ mock_lgetfilecon.assert_called_once_with(entry.get("name"))
self.assertFalse(mock_lsetfilecon.called)
mock_restorecon.reset_mock()
mock_lsetfilecon.reset_mock()
+ mock_lgetfilecon.reset_mock()
mock_lsetfilecon.return_value = 0
entry.set("secontext", "foo_t")
self.assertTrue(ptool._set_secontext(entry))
self.assertFalse(mock_restorecon.called)
+ mock_lgetfilecon.assert_called_once_with(entry.get("name"))
mock_lsetfilecon.assert_called_with(entry.get("name"), "foo_t")
mock_restorecon.reset_mock()
mock_lsetfilecon.reset_mock()
+ mock_lgetfilecon.reset_mock()
mock_lsetfilecon.return_value = 1
self.assertFalse(ptool._set_secontext(entry))
self.assertFalse(mock_restorecon.called)
+ mock_lgetfilecon.assert_called_once_with(entry.get("name"))
mock_lsetfilecon.assert_called_with(entry.get("name"), "foo_t")
+ # ignored filesystem
+ mock_restorecon.reset_mock()
+ mock_lsetfilecon.reset_mock()
+ mock_lgetfilecon.reset_mock()
+ mock_lgetfilecon.return_value = (0, "system_u:object_r:dosfs_t")
+ self.assertTrue(ptool._set_secontext(entry))
+ self.assertFalse(mock_restorecon.called)
+ self.assertFalse(mock_lsetfilecon.called)
+ mock_lgetfilecon.assert_called_once_with(entry.get("name"))
+
@patch("grp.getgrnam")
def test_norm_gid(self, mock_getgrnam):
ptool = self.get_obj()
@@ -686,7 +708,7 @@ class TestPOSIXTool(TestTool):
ptool._gather_data = Mock()
entry = lxml.etree.Element("Path", name="/test", type="file",
group="group", owner="user", mode="664",
- secontext='etc_t')
+ secontext='unconfined_u:object_r:etc_t:s0')
# _verify_metadata() mutates the entry, so we keep a backup so we
# can start fresh every time
orig_entry = copy.deepcopy(entry)
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
index cc1ea6fd7..08c20981d 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
@@ -103,26 +103,26 @@ class TestPOSIXUsers(TestTool):
# test failure of inherited method
entry = lxml.etree.Element("POSIXUser", name="test")
self.assertFalse(users.canInstall(entry))
- mock_canInstall.assertCalledWith(users, entry)
+ mock_canInstall.assert_called_with(users, entry)
# test with no uid specified
reset()
mock_canInstall.return_value = True
self.assertTrue(users.canInstall(entry))
- mock_canInstall.assertCalledWith(users, entry)
+ mock_canInstall.assert_called_with(users, entry)
# test with uid specified, not in managed range
reset()
entry.set("uid", "1000")
self.assertFalse(users.canInstall(entry))
- mock_canInstall.assertCalledWith(users, entry)
+ mock_canInstall.assert_called_with(users, entry)
users._in_managed_range.assert_called_with(entry.tag, "1000")
# test with uid specified, in managed range
reset()
users._in_managed_range.return_value = True
self.assertTrue(users.canInstall(entry))
- mock_canInstall.assertCalledWith(users, entry)
+ mock_canInstall.assert_called_with(users, entry)
users._in_managed_range.assert_called_with(entry.tag, "1000")
@patch("Bcfg2.Client.Tools.Tool.Inventory")
diff --git a/testsuite/Testsrc/Testlib/TestOptions/TestComponents.py b/testsuite/Testsrc/Testlib/TestOptions/TestComponents.py
index 61b87de2a..b1ed4cb2b 100644
--- a/testsuite/Testsrc/Testlib/TestOptions/TestComponents.py
+++ b/testsuite/Testsrc/Testlib/TestOptions/TestComponents.py
@@ -182,7 +182,8 @@ class TestImportComponentOptions(OptionTestCase):
"""test cases for component loading."""
def setUp(self):
- self.options = [Option("--cls", action=ImportComponentAction),
+ self.options = [Option("--cls", cf=("config", "cls"),
+ action=ImportComponentAction),
Option("--module", action=ImportModuleAction)]
self.result = argparse.Namespace()
@@ -227,3 +228,10 @@ class TestImportComponentOptions(OptionTestCase):
self.assertRaises(SystemExit,
self.parser.parse,
["-C", config_file, "--cls", "Bcfg2.No.Such.Thing"])
+
+ @make_config({"config": {"test": "foo", "cls": "Two"}})
+ def test_default_from_config_for_component_options(self, config_file):
+ """use default value from config file for options added by dynamic loaded component."""
+ self.parser.parse(["-C", config_file])
+ self.assertEqual(self.result.cls, Two.Two)
+ self.assertEqual(self.result.test, "foo")
diff --git a/testsuite/Testsrc/Testlib/TestOptions/TestConfigFiles.py b/testsuite/Testsrc/Testlib/TestOptions/TestConfigFiles.py
index 78acadf1f..4277c8efe 100644
--- a/testsuite/Testsrc/Testlib/TestOptions/TestConfigFiles.py
+++ b/testsuite/Testsrc/Testlib/TestOptions/TestConfigFiles.py
@@ -45,6 +45,10 @@ class TestConfigFiles(OptionTestCase):
inner1()
@mock.patch("os.path.exists", mock.Mock(return_value=False))
- def test_no_config_file(self):
+ @make_config()
+ def test_no_config_file(self, config):
"""fail to read config file."""
- self.assertRaises(SystemExit, self.parser.parse, [])
+ try:
+ self.parser.parse(['-C', config])
+ except SystemExit:
+ self.fail('Missing config file should not raise SystemExit')
diff --git a/testsuite/Testsrc/Testlib/TestOptions/TestTypes.py b/testsuite/Testsrc/Testlib/TestOptions/TestTypes.py
index 404d67fdc..0b67db38a 100644
--- a/testsuite/Testsrc/Testlib/TestOptions/TestTypes.py
+++ b/testsuite/Testsrc/Testlib/TestOptions/TestTypes.py
@@ -46,21 +46,23 @@ class TestOptionTypes(Bcfg2TestCase):
self.assertItemsEqual(self._test_options(["--test", "one:two three"]),
["one", "two three"])
- def test_comma_dict(self):
- """parse comma-dict values."""
- self.options = [Option("--test", type=Types.comma_dict)]
+ def test_literal_dict(self):
+ """parse literal-dict values."""
+ self.options = [Option("--test", type=Types.literal_dict)]
expected = {
"one": True,
"two": 2,
"three": "three",
- "four": False}
+ "four": False,
+ "five": {
+ "a": 1,
+ "b": 2
+ }}
self.assertDictEqual(
self._test_options(["--test",
- "one=yes, two=2 , three=three,four=no"]),
- expected)
-
- self.assertDictEqual(
- self._test_options(["--test", "one,two=2,three=three,four=off"]),
+ '''{ "one": True, "two": 2,
+ "three": "three", "four": False,
+ "five": { "a": 1, "b": 2 }}''']),
expected)
def test_anchored_regex_list(self):
diff --git a/testsuite/Testsrc/Testlib/TestOptions/Two.py b/testsuite/Testsrc/Testlib/TestOptions/Two.py
index 189e0817f..0120e8b77 100644
--- a/testsuite/Testsrc/Testlib/TestOptions/Two.py
+++ b/testsuite/Testsrc/Testlib/TestOptions/Two.py
@@ -1,6 +1,7 @@
"""Test module for component loading."""
+from Bcfg2.Options import Option
class Two(object):
"""Test class for component loading."""
- pass
+ options = [Option('--test', cf=("config", "test"), dest="test", default="bar")]
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
index 5a82100d0..9f6a9f320 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
@@ -51,6 +51,7 @@ class TestFunctions(Bcfg2TestCase):
class TestDatabaseBacked(TestPlugin):
test_obj = DatabaseBacked
+ synced = False
def setUp(self):
TestPlugin.setUp(self)
@@ -76,6 +77,15 @@ class TestDatabaseBacked(TestPlugin):
setattr(Bcfg2.Options.setup, attr, True)
self.assertRaises(PluginInitError, self.get_obj, core)
+ def syncdb(self, modeltest):
+ """ Given an instance of a :class:`DBModelTestCase` object, sync
+ and clean the database """
+ inst = modeltest(methodName='test_syncdb')
+ if not self.__class__.synced:
+ inst.test_syncdb()
+ self.__class__.synced = True
+ inst.test_cleandb()
+
class TestPluginDatabaseModel(Bcfg2TestCase):
""" placeholder for future tests """
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py
index 1bf208c3e..5a8c44cd5 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py
@@ -98,7 +98,7 @@ class TestBundler(TestPlugin, TestStructure, TestXMLDirectoryBacked):
lxml.etree.SubElement(expected['indep'], "Service", name="baz")
metadata = Mock()
- metadata.bundles = ["error", "xinclude", "has_dep", "indep"]
+ metadata.bundles = set(["error", "xinclude", "has_dep", "indep"])
metadata.version_info = Bcfg2VersionInfo('1.4.0')
rv = b.BuildStructures(metadata)
@@ -131,7 +131,7 @@ class TestBundler(TestPlugin, TestStructure, TestXMLDirectoryBacked):
lxml.etree.SubElement(expected['has_dep'], "Package", name="foo")
metadata = Mock()
- metadata.bundles = ["has_dep"]
+ metadata.bundles = set(["has_dep"])
metadata.version_info = Bcfg2VersionInfo('1.3.0')
rv = b.BuildStructures(metadata)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
index 9b4a6af88..3c660099e 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
@@ -22,6 +22,10 @@ from Testinterfaces import TestGoalValidator
class TestDefaults(TestRules, TestGoalValidator):
test_obj = Defaults
+ def setUp(self):
+ TestRules.setUp(self)
+ set_setup_default("defaults_replace_name", True)
+
def get_obj(self, *args, **kwargs):
return TestRules.get_obj(self, *args, **kwargs)
@@ -91,3 +95,8 @@ class TestDefaults(TestRules, TestGoalValidator):
def test_regex(self):
self._do_test('regex')
+
+ def test_replace_name(self):
+ Bcfg2.Options.setup.defaults_replace_name = True
+ self._do_test('replace_name')
+ Bcfg2.Options.setup.defaults_replace_name = False
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
index f2721c9ea..8b9a631c1 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
@@ -123,7 +123,7 @@ class TestClientVersions(TestDatabaseBacked):
def setUp(self):
TestDatabaseBacked.setUp(self)
self.test_obj = ClientVersions
- syncdb(TestMetadataDB)
+ self.syncdb(TestMetadataDB)
for client, version in self.test_clients.items():
MetadataClientModel(hostname=client, version=version).save()
@@ -302,8 +302,7 @@ class TestXMLMetadataConfig(TestXMLFileBacked):
self.assertIsNotNone(config.basedata)
reset()
- mock_parse.side_effect = lxml.etree.XMLSyntaxError(None, None, None,
- None)
+ mock_parse.side_effect = lxml.etree.XMLSyntaxError(None, 0, 0, 0)
config.load_xml()
mock_parse.assert_called_with(os.path.join(config.basedir,
"clients.xml"),
@@ -1252,7 +1251,7 @@ class TestMetadataBase(TestMetadata):
TestClientRunHooks.setUp(self)
TestDatabaseBacked.setUp(self)
Bcfg2.Options.setup.metadata_db = True
- syncdb(TestMetadataDB)
+ self.syncdb(TestMetadataDB)
def load_clients_data(self, metadata=None, xdata=None):
if metadata is None:
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
index 32766b5c1..9729a0449 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
@@ -201,7 +201,7 @@ group-specific"""
assert False, "Strange probe found in get_probe_data() return"
-class TestProbes(TestPlugin):
+class TestProbes(TestDatabaseBacked):
test_obj = Probes
test_xdata = lxml.etree.Element("test")
@@ -241,7 +241,7 @@ group: group:with:colons
self.datastore = None
Bcfg2.Options.setup.repository = datastore
- def get_obj(self):
+ def get_obj(self, core=None):
if not Bcfg2.Options.setup.probes_db:
# actually use a real datastore so we can read and write
# probed.xml
@@ -251,7 +251,7 @@ group: group:with:colons
datadir = os.path.join(self.datastore, self.test_obj.name)
if not os.path.exists(datadir):
os.makedirs(datadir)
- return TestPlugin.get_obj(self)
+ return TestPlugin.get_obj(self, core)
def test__init(self):
if Bcfg2.Options.setup.probes_db:
@@ -278,7 +278,7 @@ group: group:with:colons
def test_probes_db(self):
""" Set and retrieve probe data with database enabled """
Bcfg2.Options.setup.probes_db = True
- syncdb(TestProbesDB)
+ self.syncdb(TestProbesDB)
self._perform_tests()
def test_allowed_cgroups(self):
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
index 36baee899..8c7f3c5d7 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
@@ -29,11 +29,14 @@ class TestPropertyFile(Bcfg2TestCase):
test_obj = PropertyFile
path = os.path.join(datastore, "test")
- def get_obj(self, path=None):
+ def get_obj(self, path=None, core=None, *args, **kwargs):
set_setup_default("writes_enabled", False)
if path is None:
path = self.path
- return self.test_obj(path)
+ if core is None:
+ core = Mock()
+ core.metadata_cache_mode = 'none'
+ return self.test_obj(path, core, *args, **kwargs)
def test_write(self):
pf = self.get_obj()
@@ -97,6 +100,9 @@ class TestJSONPropertyFile(TestFileBacked, TestPropertyFile):
TestFileBacked.setUp(self)
TestPropertyFile.setUp(self)
+ def get_obj(self, *args, **kwargs):
+ return TestPropertyFile.get_obj(self, *args, **kwargs)
+
@patch("%s.loads" % JSON)
def test_Index(self, mock_loads):
pf = self.get_obj()
@@ -137,6 +143,9 @@ class TestYAMLPropertyFile(TestFileBacked, TestPropertyFile):
TestFileBacked.setUp(self)
TestPropertyFile.setUp(self)
+ def get_obj(self, *args, **kwargs):
+ return TestPropertyFile.get_obj(self, *args, **kwargs)
+
@patch("yaml.load")
def test_Index(self, mock_load):
pf = self.get_obj()
@@ -179,7 +188,7 @@ class TestXMLPropertyFile(TestPropertyFile, TestStructFile):
set_setup_default("automatch", False)
def get_obj(self, *args, **kwargs):
- return TestStructFile.get_obj(self, *args, **kwargs)
+ return TestPropertyFile.get_obj(self, *args, **kwargs)
@patch("%s.open" % builtins)
def test__write(self, mock_open):
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
index 45f3671e8..88b334edf 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
@@ -32,6 +32,7 @@ class TestRules(TestPrioDir):
group=lxml.etree.Element("SEPort", name="6789/tcp"),
children=lxml.etree.Element("Path", name="/etc/child-entries"),
regex=lxml.etree.Element("Package", name="regex"),
+ replace_name=lxml.etree.Element("POSIXUser", name="regex"),
slash=lxml.etree.Element("Path", name="/etc/trailing/slash"),
no_slash=lxml.etree.Element("Path", name="/etc/no/trailing/slash/"))
@@ -53,6 +54,8 @@ class TestRules(TestPrioDir):
group="root", mode="0775"),
regex=lxml.etree.Element("Package", name="regex", type="yum",
version="any"),
+ replace_name=lxml.etree.Element("POSIXUser", name="regex",
+ home="/foobar%{bar}/regex"),
slash=lxml.etree.Element("Path", name="/etc/trailing/slash",
type="directory", owner="root", group="root",
mode="0600"),
@@ -70,6 +73,8 @@ class TestRules(TestPrioDir):
in_file = copy.deepcopy(concrete)
in_file['regex'].set("name", ".*")
+ in_file['replace_name'].set("home", "/foobar%{bar}/%{name}")
+ in_file['replace_name'].set("name", ".*")
in_file['slash'].set("name", "/etc/trailing/slash/")
in_file['no_slash'].set("name", "/etc/no/trailing/slash")
@@ -91,6 +96,7 @@ class TestRules(TestPrioDir):
rules3 = lxml.etree.Element("Rules", priority="10")
rules3.append(in_file['duplicate'])
rules3.append(in_file['regex'])
+ rules3.append(in_file['replace_name'])
rules3.append(in_file['slash'])
rules = {"rules1.xml": rules1, "rules2.xml": rules2, "rules3.xml": rules3}
@@ -99,6 +105,7 @@ class TestRules(TestPrioDir):
TestPrioDir.setUp(self)
set_setup_default("lax_decryption", True)
set_setup_default("rules_regex", False)
+ set_setup_default("rules_replace_name", False)
def get_child(self, name):
""" Turn one of the XML documents in `rules` into a child
@@ -169,6 +176,17 @@ class TestRules(TestPrioDir):
self._do_test('regex')
Bcfg2.Options.setup.rules_regex = False
+ def test_replace_name(self):
+ """ Test that Rules handles replaces name in attribues with regular expressions """
+ Bcfg2.Options.setup.rules_regex = False
+ Bcfg2.Options.setup.rules_replace_name = False
+ self._do_test_failure('replace_name', handles=False)
+ Bcfg2.Options.setup.rules_regex = True
+ Bcfg2.Options.setup.rules_replace_name = True
+ self._do_test('replace_name')
+ Bcfg2.Options.setup.rules_regex = False
+ Bcfg2.Options.setup.rules_replace_name = False
+
def test_slash(self):
""" Test that Rules handles trailing slashes on Path entries """
self._do_test('slash')
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
index 128d6cae5..a470dca67 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
@@ -2,6 +2,7 @@ import os
import sys
import Bcfg2.Server.Plugin
from mock import Mock, MagicMock, patch
+from Bcfg2.Utils import safe_module_name
from Bcfg2.Server.Plugins.TemplateHelper import *
# add all parent testsuite directories to sys.path to allow (most)
@@ -22,10 +23,13 @@ class TestHelperModule(Bcfg2TestCase):
test_obj = HelperModule
path = os.path.join(datastore, "test.py")
- def get_obj(self, path=None):
+ def get_obj(self, path=None, core=None):
if path is None:
path = self.path
- return self.test_obj(path)
+ if core is None:
+ core = Mock()
+ core.metadata_cache_mode = 'none'
+ return self.test_obj(path, core)
def test__init(self):
hm = self.get_obj()
@@ -39,8 +43,9 @@ class TestHelperModule(Bcfg2TestCase):
mock_load_source.side_effect = ImportError
attrs = dir(hm)
hm.HandleEvent()
- mock_load_source.assert_called_with(safe_module_name(hm._module_name),
- hm.name)
+ mock_load_source.assert_called_with(
+ safe_module_name('TemplateHelper', hm._module_name),
+ hm.name)
self.assertEqual(attrs, dir(hm))
self.assertEqual(hm._attrs, [])
@@ -51,8 +56,9 @@ class TestHelperModule(Bcfg2TestCase):
mock_load_source.return_value = Mock()
attrs = dir(hm)
hm.HandleEvent()
- mock_load_source.assert_called_with(safe_module_name(hm._module_name),
- hm.name)
+ mock_load_source.assert_called_with(
+ safe_module_name('TemplateHelper', hm._module_name),
+ hm.name)
self.assertEqual(attrs, dir(hm))
self.assertEqual(hm._attrs, [])
@@ -63,8 +69,9 @@ class TestHelperModule(Bcfg2TestCase):
mock_load_source.return_value = module
attrs = dir(hm)
hm.HandleEvent()
- mock_load_source.assert_called_with(safe_module_name(hm._module_name),
- hm.name)
+ mock_load_source.assert_called_with(
+ safe_module_name('TemplateHelper', hm._module_name),
+ hm.name)
self.assertEqual(attrs, dir(hm))
self.assertEqual(hm._attrs, [])
@@ -74,8 +81,9 @@ class TestHelperModule(Bcfg2TestCase):
mock_load_source.reset()
mock_load_source.return_value = module
hm.HandleEvent()
- mock_load_source.assert_called_with(safe_module_name(hm._module_name),
- hm.name)
+ mock_load_source.assert_called_with(
+ safe_module_name('TemplateHelper', hm._module_name),
+ hm.name)
self.assertTrue(hasattr(hm, "foo"))
self.assertTrue(hasattr(hm, "bar"))
self.assertTrue(hasattr(hm, "baz"))
@@ -87,8 +95,9 @@ class TestHelperModule(Bcfg2TestCase):
mock_load_source.reset()
mock_load_source.return_value = module
hm.HandleEvent()
- mock_load_source.assert_called_with(safe_module_name(hm._module_name),
- hm.name)
+ mock_load_source.assert_called_with(
+ safe_module_name('TemplateHelper', hm._module_name),
+ hm.name)
self.assertTrue(hasattr(hm, "foo"))
self.assertTrue(hasattr(hm, "bar"))
self.assertTrue(hasattr(hm, "quux"))
diff --git a/testsuite/Testsrc/Testlib/TestUtils.py b/testsuite/Testsrc/Testlib/TestUtils.py
index 4bed67248..a37f2ecbe 100644
--- a/testsuite/Testsrc/Testlib/TestUtils.py
+++ b/testsuite/Testsrc/Testlib/TestUtils.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
import os
import sys
from Bcfg2.Utils import *
@@ -42,3 +43,17 @@ class TestPackedDigitRange(Bcfg2TestCase):
for test in exc:
self.assertNotIn(test, rng)
self.assertFalse(rng.includes(test))
+
+
+class TestIsString(Bcfg2TestCase):
+ def test_is_string(self):
+ for char in list(range(8)) + list(range(14, 32)):
+ self.assertFalse(is_string("foo" + chr(char) + "bar", 'UTF-8'))
+ for char in list(range(9, 14)) + list(range(33, 128)):
+ self.assertTrue(is_string("foo" + chr(char) + "bar", 'UTF-8'))
+
+ ustr = 'é'
+ self.assertTrue(is_string(ustr, 'UTF-8'))
+ if not inPy3k:
+ self.assertFalse(is_string("foo" + chr(128) + "bar", 'ascii'))
+ self.assertFalse(is_string(ustr, 'ascii'))
diff --git a/testsuite/Testsrc/test_code_checks.py b/testsuite/Testsrc/test_code_checks.py
index ba4b19d1c..44c4633a7 100644
--- a/testsuite/Testsrc/test_code_checks.py
+++ b/testsuite/Testsrc/test_code_checks.py
@@ -66,7 +66,6 @@ error_checks = {
"VCS.py",
"YUM24.py"],
"lib/Bcfg2/Server/Plugins": ["Deps.py",
- "Ldap.py",
"Pkgmgr.py"]
}
@@ -77,10 +76,13 @@ no_checks = {
"lib/Bcfg2": ["manage.py"],
"lib/Bcfg2/Server/Reports": ["manage.py"],
"lib/Bcfg2/Server/Plugins": ["Base.py"],
+ "lib/Bcfg2/Server/migrations": ["*.py"],
+ "lib/Bcfg2/Server/south_migrations": ["*.py"],
}
+
if sys.version_info < (2, 6):
- # multiprocessing core requires py2.6
- no_checks['lib/Bcfg2/Server'] = ['MultiprocessingCore.py']
+ # Server requires python 2.6
+ no_checks['lib/Bcfg2'] = ['Server']
try:
any
diff --git a/testsuite/Testsrc/test_doc.py b/testsuite/Testsrc/test_doc.py
new file mode 100644
index 000000000..d105254c5
--- /dev/null
+++ b/testsuite/Testsrc/test_doc.py
@@ -0,0 +1,39 @@
+import os
+import sys
+
+# add all parent testsuite directories to sys.path to allow (most)
+# relative imports in python 2.4
+_path = os.path.dirname(__file__)
+while _path != '/':
+ if os.path.basename(_path).lower().startswith("test"):
+ sys.path.append(_path)
+ if os.path.basename(_path) == "testsuite":
+ break
+ _path = os.path.dirname(_path)
+from common import *
+
+
+try:
+ from sphinx.application import Sphinx
+ HAS_SPHINX = True
+except ImportError:
+ HAS_SPHINX = False
+
+
+TEST_SPHINX = bool(os.environ.get('TEST_SPHINX', 'no') != 'no')
+
+
+class DocTest(Bcfg2TestCase):
+ top = os.path.join(os.path.dirname(__file__), '..', '..')
+ source_dir = os.path.join(top, 'doc/')
+ doctree_dir = os.path.join(top, 'build', 'doctree')
+
+ @skipUnless(HAS_SPHINX, 'Sphinx not found')
+ @skipUnless(TEST_SPHINX, 'Documentation testing disabled')
+ def test_html_documentation(self):
+ output_dir = os.path.join(self.top, 'build', 'html')
+
+ app = Sphinx(self.source_dir, self.source_dir, output_dir,
+ self.doctree_dir, buildername='html',
+ warningiserror=True)
+ app.build(force_all=True)
diff --git a/testsuite/common.py b/testsuite/common.py
index 4c7337e0d..e0ff3ed19 100644
--- a/testsuite/common.py
+++ b/testsuite/common.py
@@ -15,7 +15,10 @@ import codecs
import lxml.etree
import Bcfg2.Options
import Bcfg2.Utils
-from mock import patch, MagicMock, _patch, DEFAULT
+try:
+ from mock.mock import patch, MagicMock, _patch, DEFAULT
+except ImportError:
+ from mock import patch, MagicMock, _patch, DEFAULT
try:
from unittest2 import skip, skipIf, skipUnless, TestCase
except ImportError:
@@ -64,6 +67,7 @@ try:
set_setup_default("time_zone")
set_setup_default("web_debug", False)
set_setup_default("web_prefix")
+ set_setup_default("django_settings")
import Bcfg2.DBSettings
Bcfg2.DBSettings.finalize_django_config()
@@ -163,7 +167,7 @@ class Bcfg2TestCase(TestCase):
sys.stderr = cls._stderr
if hasattr(TestCase, "assertCountEqual"):
- assertItemsEqual = assertCountEqual
+ assertItemsEqual = TestCase.assertCountEqual
def assertXMLEqual(self, el1, el2, msg=None):
""" Test that the two XML trees given are equal. """
@@ -171,21 +175,22 @@ class Bcfg2TestCase(TestCase):
msg = "XML trees are not equal: %s"
else:
msg += ": %s"
- fullmsg = msg + "\nFirst: %s" % lxml.etree.tostring(el1) + \
+ msg += "\n%s"
+ fullmsg = "First: %s" % lxml.etree.tostring(el1) + \
"\nSecond: %s" % lxml.etree.tostring(el2)
- self.assertEqual(el1.tag, el2.tag, msg=fullmsg % "Tags differ")
+ self.assertEqual(el1.tag, el2.tag, msg=msg % ("Tags differ", fullmsg))
if el1.text is not None and el2.text is not None:
self.assertEqual(el1.text.strip(), el2.text.strip(),
- msg=fullmsg % "Text content differs")
+ msg=msg % ("Text content differs", fullmsg))
else:
self.assertEqual(el1.text, el2.text,
- msg=fullmsg % "Text content differs")
+ msg=msg % ("Text content differs", fullmsg))
self.assertItemsEqual(el1.attrib.items(), el2.attrib.items(),
- msg=fullmsg % "Attributes differ")
+ msg=msg % ("Attributes differ", fullmsg))
self.assertEqual(len(el1.getchildren()),
len(el2.getchildren()),
- msg=fullmsg % "Different numbers of children")
+ msg=msg % ("Different numbers of children", fullmsg))
matched = []
for child1 in el1.getchildren():
for child2 in el2.xpath(child1.tag):
@@ -199,10 +204,10 @@ class Bcfg2TestCase(TestCase):
continue
else:
assert False, \
- fullmsg % ("Element %s is missing from second" %
- lxml.etree.tostring(child1))
+ msg % ("Element %s is missing from second" %
+ lxml.etree.tostring(child1), fullmsg)
self.assertItemsEqual(el2.getchildren(), matched,
- msg=fullmsg % "Second has extra element(s)")
+ msg=msg % ("Second has extra element(s)", fullmsg))
class DBModelTestCase(Bcfg2TestCase):
@@ -214,12 +219,33 @@ class DBModelTestCase(Bcfg2TestCase):
def test_syncdb(self):
""" Create the test database and sync the schema """
if self.models:
+ import django
import django.core.management
- django.core.management.call_command("syncdb", interactive=False,
- verbosity=0)
- self.assertTrue(
- os.path.exists(
- django.conf.settings.DATABASES['default']['NAME']))
+ from django.core.exceptions import ImproperlyConfigured
+
+ dbfile = django.conf.settings.DATABASES['default']['NAME']
+
+ # Close all connections to the old database
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ for connection in django.db.connections.all():
+ connection.close()
+ else:
+ django.db.close_connection()
+
+ # Remove old database
+ if os.path.exists(dbfile):
+ os.unlink(dbfile)
+ self.assertFalse(os.path.exists(dbfile))
+
+ # Create new
+ if django.VERSION[0] == 1 and django.VERSION[1] < 7:
+ django.core.management.call_command('syncdb', interactive=False,
+ verbosity=1)
+ django.core.management.call_command('migrate', interactive=False,
+ verbosity=1)
+
+ # Check if database exists now
+ self.assertTrue(os.path.exists(dbfile))
@skipUnless(has_django, "Django not found, skipping")
def test_cleandb(self):
@@ -230,14 +256,6 @@ class DBModelTestCase(Bcfg2TestCase):
self.assertItemsEqual(list(model.objects.all()), [])
-def syncdb(modeltest):
- """ Given an instance of a :class:`DBModelTestCase` object, sync
- and clean the database """
- inst = modeltest(methodName='test_syncdb')
- inst.test_syncdb()
- inst.test_cleandb()
-
-
# in order for patchIf() to decorate a function in the same way as
# patch(), we override the default behavior of __enter__ and __exit__
# on the _patch() object to basically be noops.
diff --git a/testsuite/ext/exception_messages.py b/testsuite/ext/exception_messages.py
index cd3d7112c..5c59916df 100644
--- a/testsuite/ext/exception_messages.py
+++ b/testsuite/ext/exception_messages.py
@@ -35,10 +35,15 @@ class ExceptionMessageChecker(BaseChecker):
priority = -1
def visit_raise(self, node):
- if node.exc is None:
+ exc = None
+ try:
+ exc = node.exc
+ except AttributeError:
+ exc = node.type
+ if exc is None:
return
- if isinstance(node.exc, ast.Name):
- raised = safe_infer(node.exc)
+ if isinstance(exc, ast.Name):
+ raised = safe_infer(exc)
if (isinstance(raised, ast.Class) and
raised.name not in self.config.exceptions_without_args):
self.add_message('R9901', node=node.exc)
diff --git a/testsuite/ext/pylint_compat.py b/testsuite/ext/pylint_compat.py
new file mode 100644
index 000000000..5300df8e6
--- /dev/null
+++ b/testsuite/ext/pylint_compat.py
@@ -0,0 +1,280 @@
+from pylint.__pkginfo__ import version as pylint_version
+
+try:
+ from logilab.astng.__pkginfo__ import version as astng_version
+except ImportError:
+ from astroid.__pkginfo__ import version as astng_version
+
+
+def register(linter):
+ if pylint_version < '0.24.0':
+ import pylint.utils
+ orig_check_message_id = pylint.utils.MessagesHandlerMixIn.check_message_id
+ def check_message_id(self, msgid):
+ # translate the new message ids back into the old ones
+ replacements = {'12': '65', '13': '99'}
+ new = msgid[1:3]
+ if new in replacements:
+ msgid = msgid[0] + replacements[new] + msgid[3:]
+ return orig_check_message_id(self, msgid)
+ pylint.utils.MessagesHandlerMixIn.check_message_id = check_message_id
+
+ def ignore(meth, msgid, *args, **kwargs):
+ # ignore non-existent message ids in disable/enable comments
+ ignore = ['W1401', 'R0924']
+ if msgid in ignore:
+ return
+ return meth(msgid, *args, **kwargs)
+ linter._options_methods['disable'] = lambda *args, **kwargs: ignore(linter.disable, *args, **kwargs)
+ linter._options_methods['enable'] = lambda *args, **kwargs: ignore(linter.enable, *args, **kwargs)
+
+ if pylint_version < '0.22.0':
+ import pylint.checkers.exceptions
+ orig_visit_raise = pylint.checkers.exceptions.ExceptionsChecker.visit_raise
+ def visit_raise(self, node):
+ if not hasattr(node, 'type') and hasattr(node, 'exc'):
+ node.type = node.exc
+ return orig_visit_raise(self, node)
+ pylint.checkers.exceptions.ExceptionsChecker.visit_raise = visit_raise
+
+ if astng_version < '0.23':
+ import logilab.astng.scoped_nodes
+ from logilab.astng.bases import InferenceContext, InferenceError
+
+ # backport import bug fix (e642ba33ba1bdde04ac9f0c75a25dc40131c55e7)
+ def ancestors(self, recurs=True, context=None):
+ yielded = set([self])
+ if context is None:
+ context = InferenceContext()
+ for stmt in self.bases:
+ path = set(context.path)
+ try:
+ for baseobj in stmt.infer(context):
+ if not isinstance(baseobj, logilab.astng.scoped_nodes.Class):
+ # duh ?
+ continue
+ if baseobj in yielded:
+ continue # cf xxx above
+ yielded.add(baseobj)
+ yield baseobj
+ if recurs:
+ for grandpa in baseobj.ancestors(True, context):
+ if grandpa in yielded:
+ continue # cf xxx above
+ yielded.add(grandpa)
+ yield grandpa
+ except InferenceError:
+ # XXX log error ?
+ pass
+ context.path = path
+ logilab.astng.scoped_nodes.Class.ancestors = ancestors
+
+ # support for classpropery (d110bcf2de4b8bc48e41638cf430f17c5714ffbc)
+ try:
+ from logilab.astng.rebuilder import TreeRebuilder
+ except:
+ try:
+ from logilab.astng._nodes_ast import TreeRebuilder
+ except:
+ from logilab.astng._nodes_compiler import TreeRebuilder
+ from logilab.astng import nodes
+
+ orig_visit_function = TreeRebuilder.visit_function
+ def visit_function(self, node, parent):
+ newnode = orig_visit_function(self, node, parent)
+ if newnode.decorators is not None:
+ for decorator_expr in newnode.decorators.nodes:
+ if isinstance(decorator_expr, nodes.Name):
+ if decorator_expr.name == 'classproperty':
+ newnode.type = 'classmethod'
+ return newnode
+ TreeRebuilder.visit_function = visit_function
+
+ if astng_version < '0.22':
+ from logilab.astng import nodes
+ from logilab.astng.bases import _infer_stmts, copy_context, path_wrapper, \
+ InferenceError, NotFoundError
+ from logilab.astng._exceptions import ASTNGBuildingException
+ import logilab.astng.scoped_nodes
+ from logilab.astng.node_classes import List, DelName
+
+ # backport of 11886551cfdcf969f0a661f8ab63c1fa1a6dd399 with
+ # a bit revert of af896e299ce5e381a928a77a9c28941cad90a243
+ def infer_from(self, context=None, asname=True):
+ name = context.lookupname
+ if name is None:
+ raise InferenceError()
+ if asname:
+ name = self.real_name(name)
+ module = self.do_import_module(self.modname)
+ try:
+ context = copy_context(context)
+ context.lookupname = name
+ return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context)
+ except NotFoundError:
+ raise InferenceError(name)
+ nodes.From.infer = path_wrapper(infer_from)
+
+ def getattr(self, name, context=None, ignore_locals=False):
+ if name in self.special_attributes:
+ if name == '__file__':
+ return [cf(self.file)] + self.locals.get(name, [])
+ if name == '__path__' and self.package:
+ return [List()] + self.locals.get(name, [])
+ return std_special_attributes(self, name)
+ if not ignore_locals and name in self.locals:
+ return self.locals[name]
+ if self.package:
+ try:
+ return [self.import_module(name, relative_only=True)]
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ pass
+ raise NotFoundError(name)
+ logilab.astng.scoped_nodes.Module.getattr = logilab.astng.scoped_nodes.remove_nodes(getattr, DelName)
+
+ if astng_version < '0.21.1':
+ # backport of 3d463da455e33e7ddc53a295b6a33db7b9e4288b
+ from logilab.astng.scoped_nodes import Function
+ from logilab.astng.rebuilder import RebuildVisitor
+ from logilab.astng.bases import YES, Instance
+
+ orig_init = Function.__init__
+ def init(self, name, doc):
+ orig_init(self, name, doc)
+ self.instance_attrs = {}
+ Function.__init__ = init
+
+ orig_getattr = Function.getattr
+ def getattr(self, name, context=None):
+ if name != '__module__' and name in self.instance_attrs:
+ return self.instance_attrs[name]
+ return orig_getattr(self, name, context)
+ Function.getattr = getattr
+
+ def delayed_assattr(self, node):
+ """visit a AssAttr node -> add name to locals, handle members
+ definition
+ """
+ try:
+ frame = node.frame()
+ for infered in node.expr.infer():
+ if infered is YES:
+ continue
+ try:
+ if infered.__class__ is Instance:
+ infered = infered._proxied
+ iattrs = infered.instance_attrs
+ elif isinstance(infered, Instance):
+ # Const, Tuple, ... we may be wrong, may be not, but
+ # anyway we don't want to pollute builtin's namespace
+ continue
+ elif infered.is_function:
+ iattrs = infered.instance_attrs
+ else:
+ iattrs = infered.locals
+ except AttributeError:
+ # XXX log error
+ #import traceback
+ #traceback.print_exc()
+ continue
+ values = iattrs.setdefault(node.attrname, [])
+ if node in values:
+ continue
+ # get assign in __init__ first XXX useful ?
+ if frame.name == '__init__' and values and not \
+ values[0].frame().name == '__init__':
+ values.insert(0, node)
+ else:
+ values.append(node)
+ except InferenceError:
+ pass
+ RebuildVisitor.delayed_assattr = delayed_assattr
+
+ if astng_version < '0.20.4':
+ try:
+ from logilab.astng._nodes_ast import TreeRebuilder, _lineno_parent
+ except:
+ from logilab.astng._nodes_compiler import TreeRebuilder
+ _lineno_parent = (lambda *args: TreeRebuilder._set_infos(None, *args))
+ from logilab.astng import nodes
+ from logilab.astng.bases import NodeNG, Instance
+ from logilab.astng.mixins import ParentAssignTypeMixin
+
+ class Set(NodeNG, Instance, ParentAssignTypeMixin):
+ _astng_fields = ('elts',)
+ elts = None
+
+ def pytype(self):
+ return '__builtin__.set'
+
+ def itered(self):
+ return self.elts
+
+ def visit_set(self, node, parent):
+ newnode = Set()
+ _lineno_parent(node, newnode, parent)
+ newnode.elts = [self.visit(child, newnode) for child in node.elts]
+ newnode.set_line_info(newnode.last_child())
+ return newnode
+ TreeRebuilder.visit_set = visit_set
+
+ def visit_setcomp(self, node, parent):
+ newnode = nodes.SetComp()
+ _lineno_parent(node, newnode, parent)
+ newnode.elt = self.visit(node.elt, newnode)
+ newnode.generators = [self.visit(child, newnode)
+ for child in node.generators]
+ newnode.set_line_info(newnode.last_child())
+ return newnode
+ TreeRebuilder.visit_setcomp = visit_setcomp
+
+ class DictComp(NodeNG):
+ _astng_fields = ('key', 'value', 'generators')
+ key = None
+ value = None
+ generators = None
+
+ def visit_dictcomp(self, node, parent):
+ newnode = DictComp()
+ _lineno_parent(node, newnode, parent)
+ newnode.key = self.visit(node.key, newnode)
+ newnode.value = self.visit(node.value, newnode)
+ newnode.generators = [self.visit(child, newnode)
+ for child in node.generators]
+ newnode.set_line_info(newnode.last_child())
+ return newnode
+ TreeRebuilder.visit_dictcomp = visit_dictcomp
+
+ # backport of bfe9e5c53cfb75c3b45ebb5cb8e8902464782c7d
+ from logilab.astng.node_classes import From
+ orig_from_init = From.__init__
+ def from_init(self, fromname, names, level=0):
+ orig_from_init(self, fromname or '', names, level)
+ From.__init__ = from_init
+
+ # partial backport of 6d59ad07d722d01e458aaf8fd14fd7dfc7ebaa6e
+ from logilab.astng.scoped_nodes import Module
+ orig_absolute_modname = Module.absolute_modname
+ def absolute_modname(self, modname, level):
+ result = orig_absolute_modname(self, modname, level)
+ if result[-1] == '.':
+ return result[:-1]
+ return result
+ Module.absolute_modname = absolute_modname
+
+ # python2.4 compatibility (no super on old-style classes)
+ from logilab.astng.bases import Proxy, UnboundMethod
+
+ def unbound_igetattr(self, name, context=None):
+ if name == 'im_func':
+ return iter((self._proxied,))
+ return Proxy.igetattr(self, name, context)
+ UnboundMethod.igetattr = unbound_igetattr
+
+ def unbound_getattr(self, name, context=None):
+ if name == 'im_func':
+ return [self._proxied]
+ return Proxy.getattr(self, name, context)
+ UnboundMethod.getattr = unbound_getattr
diff --git a/testsuite/ext/ssl_protocols.py b/testsuite/ext/ssl_protocols.py
new file mode 100644
index 000000000..f92e3e355
--- /dev/null
+++ b/testsuite/ext/ssl_protocols.py
@@ -0,0 +1,25 @@
+try:
+ from logilab.astng import MANAGER, builder, scoped_nodes, node_classes
+ PYLINT=0
+except ImportError:
+ from astroid import MANAGER, scoped_nodes, node_classes
+ PYLINT=1
+
+def ssl_transform(module):
+ if module.name == 'ssl':
+ for proto in ('SSLv23', 'TLSv1'):
+ module.locals['PROTOCOL_%s' % proto] = [node_classes.Const(0)]
+
+def register(linter):
+ if PYLINT == 0:
+ if hasattr(MANAGER, 'register_transformer'):
+ MANAGER.register_transformer(ssl_transform)
+ else:
+ safe = builder.ASTNGBuilder.string_build
+ def _string_build(self, data, modname='', path=None):
+ if modname == 'ssl':
+ data += '\n\nPROTOCOL_SSLv23 = 0\nPROTOCOL_TLSv1 = 0'
+ return safe(self, data, modname, path)
+ builder.ASTNGBuilder.string_build = _string_build
+ else:
+ MANAGER.register_transform(scoped_nodes.Module, ssl_transform)
diff --git a/testsuite/install.sh b/testsuite/install.sh
index 42d5bbadb..ce3ed22b1 100755
--- a/testsuite/install.sh
+++ b/testsuite/install.sh
@@ -1,28 +1,77 @@
#!/bin/bash -ex
# install script for Travis-CI
+PYVER=$(python -c 'import sys;print(".".join(str(v) for v in sys.version_info[0:2]))')
+SITE_PACKAGES=$(python -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())')
-sudo apt-get update -qq
-sudo apt-get install swig libxml2-utils
+if [[ ${PYVER:0:1} == "2" && $PYVER != "2.7" && $PYVER != "2.6" ]]; then
+ pip install --index-url=https://pypi.python.org/simple -r testsuite/requirements-legacy.txt
+else
+ pip install --upgrade pip
-pip install -r testsuite/requirements.txt
+ pip_wheel() {
+ pip wheel --find-links="$HOME/.cache/wheels/" --wheel-dir="$HOME/.cache/wheels/" "$@"
+ pip install --no-index --find-links="$HOME/.cache/wheels/" "$@"
+ }
-PYVER=$(python -c 'import sys;print(".".join(str(v) for v in sys.version_info[0:2]))')
+ if [[ $PYVER == "2.6" ]]; then
+ pip_wheel -r testsuite/requirements-26.txt
+ pip_wheel unittest2
+ else
+ pip_wheel -r testsuite/requirements.txt
-if [[ ${PYVER:0:1} == "2" && $PYVER != "2.7" ]]; then
- pip install --use-mirrors unittest2
-fi
+ if [[ ${PYVER:0:1} == "3" ]]; then
+ # TODO: Move to "requirements.txt" if all the new errors are fixed.
+ pip_wheel 'pylint>1.4'
+ fi
+ fi
+
+ if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then
+ pip_wheel PyYAML pyinotify boto pylibacl Jinja2 \
+ cherrypy nose-show-skipped
+
+ if [[ $PYVER == "2.6" ]]; then
+ pip install \
+ --global-option='build_ext' \
+ --global-option='--include-dirs=/usr/include/x86_64-linux-gnu' \
+ m2crypto
+
+ pip_wheel 'django<1.7' 'South<0.8' 'mercurial<4.3' cheetah guppy 'pycparser<2.19' python-augeas
+ else
+ if [[ $PYVER == "2.7" ]]; then
+ pip_wheel m2crypto guppy
+ fi
-if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then
- sudo apt-get install -y yum libaugeas0 augeas-lenses libacl1-dev libssl-dev
-
- pip install --use-mirrors PyYAML pyinotify boto pylibacl 'django<1.5' \
- Jinja2 mercurial guppy
- easy_install https://fedorahosted.org/released/python-augeas/python-augeas-0.4.1.tar.gz
- if [[ ${PYVER:0:1} == "2" ]]; then
- # django supports py3k, but South doesn't, and the django bits
- # in bcfg2 require South
- pip install cheetah 'South<0.8'
- pip install m2crypto
+ pip_wheel django mercurial cheetah3 python-augeas
+ fi
fi
fi
+
+# Use system site-packages and pymodules
+if [[ "$WITH_SYSTEM_SITE_PACKAGES" == "yes" ]]; then
+ cat <<EOF > "$SITE_PACKAGES/system-packages.pth"
+/usr/lib/python$PYVER/site-packages/
+/usr/lib/python$PYVER/dist-packages/
+/usr/lib/pymodules/python$PYVER/
+EOF
+fi
+
+# Setup the local xml schema cache
+download_schema() {
+ if [[ ! -e "$1" ]]; then
+ wget -O "$1" "$2"
+ fi
+}
+
+mkdir -p "$HOME/.cache/xml/"
+download_schema "$HOME/.cache/xml/XMLSchema.xsd" "http://www.w3.org/2001/XMLSchema.xsd"
+download_schema "$HOME/.cache/xml/xml.xsd" "http://www.w3.org/2001/xml.xsd"
+
+cat > "$HOME/.cache/xml/catalog.xml" <<EOF
+<?xml version="1.0"?>
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+ <system systemId="http://www.w3.org/2001/XMLSchema.xsd" uri="$HOME/.cache/xml/XMLSchema.xsd" />
+ <system systemId="http://www.w3.org/2001/xml.xsd" uri="$HOME/.cache/xml/xml.xsd" />
+ <nextCatalog catalog="/etc/xml/catalog.xml" />
+</catalog>
+EOF
diff --git a/testsuite/prepare-python.sh b/testsuite/prepare-python.sh
new file mode 100755
index 000000000..7b72a6dc4
--- /dev/null
+++ b/testsuite/prepare-python.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ -n "$PYTHON" ]; then
+ echo "Try to use custom python version: $PYTHON"
+ pip install 'virtualenv<1.8'
+ mkdir -p "$HOME/custom-virtualenv"
+ virtualenv -p "python$PYTHON" --use-distribute "$HOME/custom-virtualenv"
+fi
diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf
index 1d3ba8c88..ce7e407df 100644
--- a/testsuite/pylintrc.conf
+++ b/testsuite/pylintrc.conf
@@ -19,7 +19,7 @@ persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
-load-plugins=ext.exception_messages
+load-plugins=ext.exception_messages,ext.ssl_protocols,ext.pylint_compat
[MESSAGES CONTROL]
diff --git a/testsuite/requirements-26.txt b/testsuite/requirements-26.txt
new file mode 100644
index 000000000..f85dc6de4
--- /dev/null
+++ b/testsuite/requirements-26.txt
@@ -0,0 +1,9 @@
+lxml
+python-daemon<2.0.0
+argparse
+genshi
+
+nose
+mock
+pylint<0.29
+pep8
diff --git a/testsuite/requirements-legacy.txt b/testsuite/requirements-legacy.txt
new file mode 100644
index 000000000..7d918cb45
--- /dev/null
+++ b/testsuite/requirements-legacy.txt
@@ -0,0 +1,14 @@
+lxml<3.4
+lockfile<0.9
+python-daemon<1.4
+argparse
+ssl
+
+nose
+nose-exclude<0.2
+mock<1.1
+unittest2<0.6
+logilab-common==0.53.0
+logilab-astng==0.20.3
+pylint<0.22
+pep8<1.3
diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt
index dce47c338..665ed961d 100644
--- a/testsuite/requirements.txt
+++ b/testsuite/requirements.txt
@@ -1,9 +1,10 @@
lxml
+python-daemon
+genshi
+argparse
+
nose
mock
-sphinx
pylint<0.29
pep8
-python-daemon
-genshi
-argparse
+sphinx
diff --git a/testsuite/test.sh b/testsuite/test.sh
new file mode 100755
index 000000000..739c3c2b0
--- /dev/null
+++ b/testsuite/test.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+NOSE_OPTS=""
+
+if [ "$WITH_OPTIONAL_DEPS" = "yes" ]; then
+ NOSE_OPTS="--show-skipped"
+fi
+
+exec nosetests $NOSE_OPTS testsuite
diff --git a/tools/export.py b/tools/export.py
index df3c810d5..77c7371b4 100755
--- a/tools/export.py
+++ b/tools/export.py
@@ -344,8 +344,8 @@ E.G. 1.2.0pre1 is a valid version.
commando_orders = ["vcs_commit",
"vcs_tag",
"create_archive",
- "gpg_encrypt",
- "scp_archive"]
+ "gpg_encrypt"]
+ #"scp_archive"]
if options.dryrun:
for cmd in commando_orders:
diff --git a/tools/upgrade/1.3/migrate_dbstats.py b/tools/upgrade/1.3/migrate_dbstats.py
index de8aef973..d4b5f12d0 100755
--- a/tools/upgrade/1.3/migrate_dbstats.py
+++ b/tools/upgrade/1.3/migrate_dbstats.py
@@ -8,12 +8,12 @@ import logging
import time
import Bcfg2.Logger
import Bcfg2.Options
-from django.db import connection, transaction, backend
+from Bcfg2.DBSettings import get_db_label
+import django
+from django.db import transaction, connections
from Bcfg2.Server.Admin import UpdateReports
-from Bcfg2.Reporting import models as new_models
from Bcfg2.Reporting.utils import BatchFetch
from Bcfg2.Reporting.Compat import transaction
-from Bcfg2.Server.Reports.reports import models as legacy_models
logger = logging.getLogger(__name__)
@@ -29,10 +29,15 @@ def _quote(value):
"""
global _our_backend
if not _our_backend:
- try:
- _our_backend = backend.DatabaseOperations(connection)
- except TypeError:
- _our_backend = backend.DatabaseOperations()
+ if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
+ _our_backend = connections[get_db_label('Reporting')].ops
+ else:
+ from django.db import backend
+ try:
+ _our_backend = backend.DatabaseOperations(
+ connections[get_db_label('Reporting')])
+ except TypeError:
+ _our_backend = backend.DatabaseOperations()
return _our_backend.quote_name(value)
@@ -74,13 +79,15 @@ def _migrate_transaction(inter, entries, fperms):
modified_count=inter.modified_entries,
extra_count=inter.extra_entries)
- if inter.metadata:
- newint.profile_id = inter.metadata.profile.id
- groups = [grp.pk for grp in inter.metadata.groups.all()]
- bundles = [bun.pk for bun in inter.metadata.bundles.all()]
- else:
- groups = []
- bundles = []
+ groups = []
+ bundles = []
+ try:
+ if inter.metadata:
+ newint.profile_id = inter.metadata.profile.id
+ groups = [grp.pk for grp in inter.metadata.groups.all()]
+ bundles = [bun.pk for bun in inter.metadata.bundles.all()]
+ except ObjectDoesNotExist:
+ pass
super(new_models.Interaction, newint).save()
if bundles:
newint.bundles.add(*bundles)
@@ -180,7 +187,7 @@ def _shove(old_table, new_table, columns):
cols,
_quote(old_table))
- cursor = connection.cursor()
+ cursor = connections[get_db_label('Reporting')].cursor()
cursor.execute(sql)
cursor.close()
@@ -283,6 +290,9 @@ if __name__ == '__main__':
description="Migrate from Bcfg2 1.2 DBStats plugin to 1.3 Reporting "
"subsystem",
components=[UpdateReports])
+ parser.parse()
+ from Bcfg2.Reporting import models as new_models
+ from Bcfg2.Server.Reports.reports import models as legacy_models
UpdateReports().run(Bcfg2.Options.setup)
_restructure()
diff --git a/tools/upgrade/1.3/migrate_info.py b/tools/upgrade/1.3/migrate_info.py
index 7f3bb9a29..2f8035bcd 100755
--- a/tools/upgrade/1.3/migrate_info.py
+++ b/tools/upgrade/1.3/migrate_info.py
@@ -48,9 +48,11 @@ def main():
parser.parse()
for plugin in Bcfg2.Options.setup.plugins:
- if plugin not in ['SSLCA', 'Cfg', 'TGenshi', 'TCheetah', 'SSHbase']:
+ plugin_name = plugin.__name__
+ if plugin_name not in ['SSLCA', 'Cfg', 'TGenshi', 'TCheetah',
+ 'SSHbase']:
continue
- datastore = os.path.join(Bcfg2.Options.setup.repository, plugin)
+ datastore = os.path.join(Bcfg2.Options.setup.repository, plugin_name)
for root, dirs, files in os.walk(datastore):
for fname in files:
if fname in [":info", "info"]:
diff --git a/tools/upgrade/1.3/migrate_perms_to_mode.py b/tools/upgrade/1.3/migrate_perms_to_mode.py
index 2dfb70388..c825c84d2 100755
--- a/tools/upgrade/1.3/migrate_perms_to_mode.py
+++ b/tools/upgrade/1.3/migrate_perms_to_mode.py
@@ -64,24 +64,26 @@ def main():
parser = Bcfg2.Options.get_parser(
description="Migrate from Bcfg2 1.2 'perms' attribute to 1.3 'mode' "
"attribute",
- components=FileMonitor)
+ components=[FileMonitor])
parser.add_options([Bcfg2.Options.Common.repository,
Bcfg2.Options.Common.plugins])
parser.parse()
repo = Bcfg2.Options.setup.repository
for plugin in Bcfg2.Options.setup.plugins:
- if plugin in ['Base', 'Bundler', 'Rules']:
- for root, _, files in os.walk(os.path.join(repo, plugin)):
+ plugin_name = plugin.__name__
+ if plugin_name in ['Base', 'Bundler', 'Rules']:
+ for root, _, files in os.walk(os.path.join(repo, plugin_name)):
if skip_path(root):
continue
for fname in files:
if skip_path(fname):
continue
convertstructure(os.path.join(root, fname))
- if plugin not in ['Cfg', 'TGenshi', 'TCheetah', 'SSHbase', 'SSLCA']:
+ if plugin_name not in ['Cfg', 'TGenshi', 'TCheetah', 'SSHbase',
+ 'SSLCA']:
continue
- for root, dirs, files in os.walk(os.path.join(repo, plugin)):
+ for root, dirs, files in os.walk(os.path.join(repo, plugin_name)):
if skip_path(root):
continue
for fname in files: