summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README8
-rw-r--r--debian/changelog6
-rw-r--r--doc/appendix/guides/import-existing-ssh-keys.txt6
-rw-r--r--doc/client/tools/augeas.txt33
-rw-r--r--doc/conf.py2
-rw-r--r--doc/contents.txt1
-rw-r--r--doc/development/testing.txt4
-rw-r--r--doc/installation/building-packages.txt228
-rw-r--r--doc/installation/distributions.txt7
-rw-r--r--doc/installation/index.txt2
-rw-r--r--doc/installation/packages.txt81
-rw-r--r--doc/installation/prerequisites.txt6
-rw-r--r--doc/installation/source.txt11
-rw-r--r--doc/releases/1.3.4.txt49
-rw-r--r--doc/releases/index.txt10
-rw-r--r--doc/server/configuration.txt43
-rw-r--r--doc/server/plugins/generators/rules.txt4
-rw-r--r--doc/server/plugins/generators/sshbase.txt8
-rw-r--r--doc/unsorted/emacs_snippet.txt2
-rw-r--r--doc/unsorted/vim_snippet.txt6
-rw-r--r--misc/bcfg2-selinux.spec4
-rw-r--r--misc/bcfg2.spec342
-rw-r--r--osx/Makefile4
-rw-r--r--osx/macports/Portfile2
-rwxr-xr-xredhat/scripts/bcfg2-server.init8
-rw-r--r--redhat/systemd/bcfg2.service3
-rw-r--r--schemas/augeas.xsd9
-rw-r--r--schemas/types.xsd1
-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/Tools/POSIX/Augeas.py32
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py116
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIXUsers.py3
-rw-r--r--src/lib/Bcfg2/Client/Tools/Systemd.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/VCS.py5
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUM.py57
-rw-r--r--src/lib/Bcfg2/Client/XML.py22
-rw-r--r--src/lib/Bcfg2/Reporting/Collector.py35
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py8
-rw-r--r--src/lib/Bcfg2/Reporting/templates/base.html2
-rw-r--r--src/lib/Bcfg2/Server/Core.py60
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py2
-rw-r--r--src/lib/Bcfg2/Server/Lint/RequiredAttrs.py34
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py29
-rw-r--r--src/lib/Bcfg2/Server/Lint/ValidateJSON.py4
-rw-r--r--src/lib/Bcfg2/Server/MultiprocessingCore.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugin/helpers.py3
-rw-r--r--src/lib/Bcfg2/Server/Plugin/interfaces.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py41
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Ohai.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Apt.py1
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py8
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Probes.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py4
-rw-r--r--src/lib/Bcfg2/version.py2
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py397
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py50
-rw-r--r--testsuite/pylintrc.conf11
-rwxr-xr-xtools/export.py41
65 files changed, 1368 insertions, 520 deletions
diff --git a/README b/README
index c836961f7..bc2f8951c 100644
--- a/README
+++ b/README
@@ -21,11 +21,11 @@ Installation
------------
For details about the installation of Bcfg2 please refer to the
-following pages in the Bcfg2 wiki.
+following pages in the Bcfg2 online documentation:
-* Prerequisites: http://bcfg2.org/wiki/Prereqs
-* Download: http://bcfg2.org/wiki/Download
-* Installation: http://bcfg2.org/wiki/Install
+* Prerequisites: http://docs.bcfg2.org/installation/prerequisites.html
+* Download: http://bcfg2.org/download/
+* Installation: http://docs.bcfg2.org/installation/index.html
Need help
---------
diff --git a/debian/changelog b/debian/changelog
index 7f6e2f637..b6d7644b9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+bcfg2 (1.3.4-0.0) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sol Jerome <sol.jerome@gmail.com> Tue, 25 Feb 2014 13:25:16 -0600
+
bcfg2 (1.3.3-0.0) unstable; urgency=low
* New upstream release
diff --git a/doc/appendix/guides/import-existing-ssh-keys.txt b/doc/appendix/guides/import-existing-ssh-keys.txt
index 0b396d327..4e2282044 100644
--- a/doc/appendix/guides/import-existing-ssh-keys.txt
+++ b/doc/appendix/guides/import-existing-ssh-keys.txt
@@ -1,4 +1,5 @@
.. -*- mode: rst -*-
+.. vim: ft=rst
.. _appendix-guides-import-existing-ssh-keys:
@@ -36,6 +37,9 @@ files explicity:
.. code-block:: xml
<Bundle>
+ <!-- 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"/>
<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'/>
@@ -93,7 +97,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_rsa_key ssh_host_dsa_key ssh_host_key; do
+ for key in 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/client/tools/augeas.txt b/doc/client/tools/augeas.txt
index 94ed9066f..6fed5f5ce 100644
--- a/doc/client/tools/augeas.txt
+++ b/doc/client/tools/augeas.txt
@@ -26,11 +26,20 @@ give it a sequence of commands:
The commands are run in document order. There's no need to do an
explicit ``save`` at the end.
-Each of these commands will only be run if the path does not already
-have the given setting. That is, the ip address for the first host
-record will only be set to ``192.168.0.1`` if it's not set to that
-value already. Its canonical name will only be set to
-``pigiron.example.com`` if it's not that already; and so on.
+These commands will be run if any of the paths do not already
+have the given setting. In other words, if any command has not
+already been run, they will all be run.
+
+So, if the first host already has all of the specified settings, then
+that Path will verify successfully and nothing will be changed. But
+suppose the first host looks like this::
+
+ 192.168.0.1 pigiron.example.com pigiron
+
+All that is missing is the second alias, ``piggy``. The entire Augeas
+script will be run in this case. It's important, then, to ensure that
+all commands you use are idempotent. (For instance, the ``Move`` and
+``Insert`` commands are unlikely to be useful.)
The Augeas paths are all relative to ``/files/etc/hosts``.
@@ -39,6 +48,20 @@ tags are: ``Remove``, ``Move``, ``Set``, ``Clear``, ``SetMulti``, and
``Insert``. Refer to the official Augeas docs or the `Schema`_ below
for details on the commands.
+The Augeas tool also supports one additional directive, ``Initial``,
+for setting initial file content when a file does not exist. For
+instance, the ``Xml`` lens fails to parse a file that does not exist,
+and, as a result, you cannot add content to it. You can use
+``Initial`` to circumvent this issue:
+
+.. code-block:: xml
+
+ <Path type="augeas" name="/etc/test.xml" lens="Xml"
+ owner="root" group="root" mode="0640">
+ <Initial>&lt;Test/&gt;</Initial>
+ <Set path="Test/#text" value="text content"/>
+ </Path>
+
Editing files outside the default load path
===========================================
diff --git a/doc/conf.py b/doc/conf.py
index 0e4009cd3..1b19d92c7 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -66,7 +66,7 @@ else:
# The short X.Y version.
version = '1.3'
# The full version, including alpha/beta/rc tags.
-release = '1.3.3'
+release = '1.3.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/contents.txt b/doc/contents.txt
index 8220d0d1d..e7df568f9 100644
--- a/doc/contents.txt
+++ b/doc/contents.txt
@@ -21,6 +21,7 @@ Bcfg2 documentation |release|
glossary
appendix/index
man/index
+ releases/index
unsorted/index
diff --git a/doc/development/testing.txt b/doc/development/testing.txt
index f00193574..b6db98cca 100644
--- a/doc/development/testing.txt
+++ b/doc/development/testing.txt
@@ -69,8 +69,8 @@ Server Testing
Entry: fs13.bgl.mcs.anl.gov.xml
Entry: fs13.bgl.mcs.anl.gov.xml good
Entry: login1.bgl.mcs.anl.gov.xml
- ConfigFile /bin/whatami contents differ
- ConfigFile /bin/whatami differs (in bundle softenv)
+ Path /bin/whatami contents differ
+ Path /bin/whatami differs (in bundle softenv)
Entry: login1.bgl.mcs.anl.gov.xml bad
This can be used to compare configurations for single clients, or
diff --git a/doc/installation/building-packages.txt b/doc/installation/building-packages.txt
new file mode 100644
index 000000000..b3b775869
--- /dev/null
+++ b/doc/installation/building-packages.txt
@@ -0,0 +1,228 @@
+.. -*- mode: rst -*-
+.. vim: ft=rst
+
+.. _installation-building-packages:
+
+=============================
+Building packages from source
+=============================
+
+Building RPMs
+=============
+
+Building from a tarball
+-----------------------
+
+* Create a directory structure for rpmbuild::
+
+ rpmdev-setuptree
+
+* Copy the tarball to ``~/rpmbuild/SOURCES/``
+* Extract another copy of it somewhere else (eg: ``/tmp``) and retrieve
+ the ``misc/bcfg2.spec`` file
+* Run the following::
+
+ rpmbuild -ba bcfg2.spec
+
+* The resulting RPMs will be in ``~/rpmbuild/RPMS/`` and SRPMs
+ in ``~/rpmbuild/SRPMS/``.
+
+Building Debian packages
+========================
+
+The Bcfg2 project provides a ``debian`` subdirectory with the project's
+source that enables users to create their own Debian/Ubuntu compatible
+packages (`.deb` files).
+
+Build deps
+----------
+
+If the distribution you are building on already has packaged bcfg2
+(even an older version), the following command will likely install the
+necessary build dependencies::
+
+ apt-get build-dep bcfg2 bcfg2-server
+
+Install source code
+-------------------
+
+Depending on which version of bcfg2 you want build, you can obtain the
+source code from the Download_ page or from the project's git repository.
+To create a local anonymous working copy of the latest version of the
+bcfg2 source code, use a command like the following::
+
+ git clone git://git.mcs.anl.gov/bcfg2.git
+
+Update the changelog
+--------------------
+
+The next step is to update the ``debian/changelog`` file with an
+appropriate package version string. Debian packages contain a version
+that is extracted from the latest entry in this file. An appropriate
+version will help you distinguish your locally built package from one
+provided by your distribution. It also helps the packaging system know
+when a newer version of the package is available to install.
+
+It is possible to skip this step, but the packages you build will have
+the same version as the source distribution and will be easy to confuse
+with other similarly named (but maybe not equivalent) packages.
+
+The basic format of the package version string to use is this::
+
+ <UPSTREAM VER>~<UPSTREAM PRE-VER>+<GIT-ID>-0.1+<LOCAL VER>
+
+.. note::
+ The '+', and '-' characters have significance in determining when
+ one package is newer than another. The following format is believed
+ to do the right thing in all common situations.
+
+The components of the package version string are explained below:
+
+.. glossary::
+
+ <UPSTREAM VER>
+ This is the version of the Bcfg source code you are working
+ from. It will likely be something like `0.9.6` or `1.0`.
+
+ <UPSTREAM PRE-VER>
+ If you are using a published pre-release of Bcfg2, it will have
+ a name like `pre1` or `rc1`. Use that string here, otherwise
+ drop this component from the package version string.
+
+ +<GIT-ID>
+ If you are building from a local working copy of the git
+ repository, it is useful to include the revision in the package
+ version. If you are building from a downloaded copy of the source,
+ drop this component (including the preceding plus-sign (`+`)
+ from the package version string.
+
+ +<LOCAL VER>
+ This is a locally relevant name like your last name or your
+ domain name, plus the digit `1`. For example, if your family
+ name is ''Smith'', you could use `smith1`. If you work for
+ ''Example Inc'', you could use `example1`.
+
+Here are some examples:
+
+* If you are building packages for revision 6c681bd from git, and the
+ latest published version is 1.2.0rc1, the version string should be
+ `1.2.0rc1+6c681bd-0.1+example1`.
+* If you are building packages for the published 1.0 rc1 version, the
+ version string should be `1.0rc1-0.1+example1`.
+* If you are building packages for the published 1.0 version, the version
+ string should be `1.0-0.1+example1`.
+
+If you are working on a git working copy of 1.0 pre5 and have the
+``devscripts`` package installed, the following command is a convenient
+way to create a well formatted changelog entry::
+
+ REV=$(git log --oneline | head -n 1 | cut -d' ' -f1)
+ debchange --force-bad-version --preserve --newversion "1.0~pre5+${REV}-0.1+example1" git revision $REV
+
+Building the package
+--------------------
+
+With the preliminaries out of the way, building the package is simple.::
+
+ cd .. # Change into the top level of the source directory
+ fakeroot dpkg-buildpackage -uc -us
+
+The freshly built packages will be deposited in the parent of the
+current directory (``..``). Examine the output of ``dpkg-buildpackage``
+for details.
+
+External build systems
+----------------------
+
+This section describes how to build bcfg2 and deps via external build
+systems (Currently only a PPA). Some other possibilities are:
+
+ * #651 Look into project-builder to make more native-system bcfg2 packages available
+ * http://en.opensuse.org/Build_Service/Deb_builds
+
+Launchpad PPA
+^^^^^^^^^^^^^
+
+https://launchpad.net/~bcfg2
+
+To upload to the PPA you need to be on the active member list of `Bcfg2
+in Launchpad`_.
+
+Note that **after each successful upload**, you should wait until the PPA
+is built, and then **install it locally** using ``sudo aptitude update;
+sudo aptitude install (packagename)`` so the next build doesn't fail on
+your local machine. If you don't want to wait for a PPA binary build to
+complete, you can "apt-get source (packagename)" and do a local build
+before the PPA build is done.
+
+setup gpg-agent
+"""""""""""""""
+
+Setting up gpg-agent and pinentry prevents you from having to type your
+passphrase repeatedly.::
+
+ sudo aptitude install gnupg-agent pinentry-gtk2 pinentry-curses
+ # replace 0xAA95C349 with your GPG Key ID
+ export GPGKEY=0xAA95C349
+ killall -q gpg-agent
+ eval $(gpg-agent --daemon)
+
+setup debuild
+"""""""""""""
+
+Tell dpkg-buildpackage who you are, for example::
+
+ export DEBEMAIL="dclark@pobox.com"
+ export DEBFULLNAME="Daniel Joseph Barnhart Clark"
+
+upload bcfg2 to ppa
+"""""""""""""""""""
+
+A ``dists`` file contains a space-separated list of all distributions
+you want to build PPA packages for.
+
+.. code-block:: sh
+
+ #!/bin/sh
+
+ . ./dists
+
+ # Replace 0xAA95C349 with your GnuPG Key ID
+ export GPGKEY=0xAA95C349
+
+ sudo apt-get build-dep bcfg2 bcfg2-server
+ sudo aptitude install git
+
+ VERSION=1.3.2-1
+ if [ ! -d testing ]; then
+ mkdir testing
+ fi
+ DATE=$(date +%F-%H%M)
+ ppa="testing" # "testing" or "ppa" (for stable)
+
+ # download source
+ cd testing
+ git clone git://git.mcs.anl.gov/bcfg2
+ cd bcfg2
+ GITID=$(git log --oneline | head -n 1 | cut -d' ' -f1)
+ cp debian/changelog ../changelog.orig
+
+ for dist in $DISTS
+ do
+ cp ../changelog.orig debian/changelog
+ (cd debian && dch --distribution ${dist} \
+ --force-bad-version \
+ --preserve \
+ --force-distribution \
+ --newversion "${VERSION}~${ppa}~${dist}${DATE}+${GITID}" \
+ "bcfg2 backport for ${dist} release ${VERSION} git commit ${GITID}")
+ debuild --no-tgz-check -rfakeroot -I -S -k${GPGKEY}
+ done
+
+ for dist in $DISTS
+ do
+ dput ppa:bcfg2/${dist}testing ../bcfg2_${VERSION}~${ppa}~${dist}${DATE}+${GITID}_source.changes
+ done
+
+.. _Download: http://bcfg2.org/download/
+.. _Bcfg2 in Launchpad: https://launchpad.net/~bcfg2
diff --git a/doc/installation/distributions.txt b/doc/installation/distributions.txt
index 9db111682..306439485 100644
--- a/doc/installation/distributions.txt
+++ b/doc/installation/distributions.txt
@@ -1,4 +1,5 @@
.. -*- mode: rst -*-
+.. vim: ft=rst
.. _distributions:
@@ -103,10 +104,12 @@ section will try and meet the dependencies using packages from EPEL_
[#f1]_. The *el5* and the *el6* package should be compatible
with `CentOS`_ 5.x/6.x and `Scientific Linux`_.
-EPEL_ for 5.x ::
+EPEL_ for 5.x::
+
[root@centos ~]# rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
-EPEL_ for 6.x ::
+EPEL_ for 6.x::
+
[root@centos ~]# rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-5.noarch.rpm
Install the bcfg2-server and bcfg2 RPMs::
diff --git a/doc/installation/index.txt b/doc/installation/index.txt
index 9f04d4b52..9bcf8be15 100644
--- a/doc/installation/index.txt
+++ b/doc/installation/index.txt
@@ -19,5 +19,5 @@ needs to be installed on any machine you plan to manage by Bcfg2.
prerequisites
source
- packages
+ building-packages
distributions
diff --git a/doc/installation/packages.txt b/doc/installation/packages.txt
deleted file mode 100644
index b23a870cf..000000000
--- a/doc/installation/packages.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-.. -*- mode: rst -*-
-
-.. _packages:
-
-.. _CentOS: http://www.centos.org/
-.. _Red Hat/RHEL: http://www.redhat.com/rhel/
-.. _Scientific Linux: http://www.scientificlinux.org/
-.. _EPEL: http://fedoraproject.org/wiki/EPEL
-.. _RPMForge: https://rpmrepo.org/RPMforge
-
-
-Building RPM packages from source
-=================================
-
-The Bcfg2 distribution contains two different spec files.
-
-Building from Tarball
----------------------
-
-* Copy the tarball to ``/usr/src/packages/SOURCES/``
-* Extract another copy of it somewhere else (eg: ``/tmp``) and retrieve
- the ``misc/bcfg2.spec`` file
-* Run ::
-
- rpmbuild -ba bcfg2.spec
-
-* The resulting RPMs will be in ``/usr/src/packages/RPMS/`` and SRPMs
- in ``/usr/src/packages/SRPMS``
-
-Building from an GIT Checkout
------------------------------
-
-* Change to the ``redhat/`` directory in the working copy
-* Run ::
-
- make
-
-* The resulting RPMs will be in ``/usr/src/redhat/RPMS/`` and SRPMs
- in ``/usr/src/redhat/SRPMS`` and will have the SVN revision appended
-
-Building RPM packages with ``rpmbuild``
----------------------------------------
-
-While you can go about building all these things from source, this
-how to will try and meet the dependencies using packages from EPEL_.
-The *el5* and the *el6* package should be compatible with CentOS 5.x.
-
-* Installation of the EPEL_ repository package ::
-
- [root@centos ~]# rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-6.noarch.rpm
-
-* Now you can install the rest of the prerequisites ::
-
- [root@centos ~]# yum install python-genshi python-cheetah python-lxml
-
-* After installing git, check out the master branch ::
-
- [root@centos redhat]# git clone git://git.mcs.anl.gov/bcfg2.git
-
-* Install the ``fedora-packager`` package ::
-
- [root@centos ~]# yum install fedora-packager
-
-* A directory structure for the RPM build process has to be established. ::
-
- [you@centos ~]$ rpmdev-setuptree
-
-* Change to the *redhat* directory of the checked out Bcfg2 source::
-
- [you@centos ~]$ cd bcfg2/redhat/
-
-* In the particular directory is a ``Makefile`` which will do the job of
- building the RPM packages. You can do this as root, but it's not
- recommended::
-
- [you@centos redhat]$ make
-
-* Now the new RPM package can be installed. Please adjust the path to
- your RPM package ::
-
- [root@centos ~]# rpm -ihv /home/YOU/rpmbuild/RPMS/noarch/bcfg2-server-1.0.0-0.2r5835.noarch.rpm
diff --git a/doc/installation/prerequisites.txt b/doc/installation/prerequisites.txt
index e3434edd3..4121ff20a 100644
--- a/doc/installation/prerequisites.txt
+++ b/doc/installation/prerequisites.txt
@@ -30,6 +30,8 @@ Bcfg2 Client
| debsums (if APT tool | Any | |
| driver is used) | | |
+----------------------------+------------------------+--------------------------------+
+| python-setuptools | Any | |
++----------------------------+------------------------+--------------------------------+
.. [#f1] python 2.5 and later works with elementtree.
@@ -56,6 +58,8 @@ Bcfg2 Server
+-------------------------------+----------+--------------------------------+
| python-ssl (note | Any | python, backported ssl module |
+-------------------------------+----------+--------------------------------+
+| python-setuptools | Any | |
++-------------------------------+----------+--------------------------------+
Bcfg2 Reporting
---------------
@@ -68,7 +72,7 @@ reporting, such as Apache + mod_wsgi or nginx.
+===============================+==========+================================+
| django | 1.2.0+ | |
+-------------------------------+----------+--------------------------------+
-| south | 0.7.0+ | |
+| south | 0.7.5+ | |
+-------------------------------+----------+--------------------------------+
Bcfg2 Reporting
diff --git a/doc/installation/source.txt b/doc/installation/source.txt
index 1406a5ceb..9bf023fbc 100644
--- a/doc/installation/source.txt
+++ b/doc/installation/source.txt
@@ -1,8 +1,9 @@
.. -*- mode: rst -*-
+.. vim: ft=rst
-.. _GPG1: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x75BF2C177F7D197E
-.. _GPG2: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x80B8492FA88FFF4B
-.. _Download: http://trac.mcs.anl.gov/projects/bcfg2/wiki/Download
+.. _7F7D197E: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x75BF2C177F7D197E
+.. _A88FFF4B: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x80B8492FA88FFF4B
+.. _Download: http://bcfg2.org/download/
.. _source:
@@ -17,8 +18,8 @@ Tarball
The Bcfg2 source tarball can be grabbed from the `Download`_ page.
-All tarballs are signed with GPG keys `7F7D197E <GPG1>`_ or `A88FFF4B
-<GPG2>`_. You can verify your download by importing the keys and running ::
+All tarballs are signed with GPG keys `7F7D197E`_ or `A88FFF4B`_. You
+can verify your download by importing the keys and running ::
gpg --recv-keys 0x75bf2c177f7d197e 0x80B8492FA88FFF4B
gpg --verify bcfg2-<version>.tar.gz.gpg bcfg2-<version>.tar.gz
diff --git a/doc/releases/1.3.4.txt b/doc/releases/1.3.4.txt
new file mode 100644
index 000000000..f6bc13436
--- /dev/null
+++ b/doc/releases/1.3.4.txt
@@ -0,0 +1,49 @@
+.. -*- mode: rst -*-
+.. vim: ft=rst
+
+.. _releases-1.3.4:
+
+1.3.4
+=====
+
+We are happy to announce the release of Bcfg2 1.3.4. It is available for
+download at:
+
+ ftp://ftp.mcs.anl.gov/pub/bcfg
+
+This is primarily a bugfix release.
+
+* New probes.allowed_groups option to restrict group assignments
+
+* Bundler fixes:
+
+ * Fix parsing XML template output with encoding declaration
+
+* bcfg2-lint:
+
+ * Resolve XIncludes when parsing XML for validation
+ * New TemplateAbuse plugin to detect templated scripts
+ * New ValidateJSON plugin
+
+* bcfg2-crypt fixes:
+
+ * Fix logic
+ * Improve debugging/error handling with Properties files
+ * Fix exception handling
+ * Handle error when encrypting properties with multiple keys
+
+* Add new Augeas client tool driver:
+ http://docs.bcfg2.org/client/tools/augeas.html
+* Restored bcfg2-admin client add functionality
+* Migration tool fixes
+* Schema fixes
+* Add Django 1.6 support
+* Use 'public' default pgsql database schema
+* Refresh essential packages during Packages.Refresh
+* Allow lxml.etree XML implementation to parse very large documents
+* Support ACLs without a specific user/group
+* Explicitly close database connections at the end of each client run
+* Fix verification of symlinks
+
+Special thanks to the following contributors for this release: Matt Baker,
+Simon Ruderich, Michael Fenn, Dan Foster, Richard Connon, John Morris.
diff --git a/doc/releases/index.txt b/doc/releases/index.txt
new file mode 100644
index 000000000..42a2306f6
--- /dev/null
+++ b/doc/releases/index.txt
@@ -0,0 +1,10 @@
+.. -*- mode: rst -*-
+.. vim: ft=rst
+
+.. _releases-index:
+
+=====================
+Release Announcements
+=====================
+
+.. include:: 1.3.4.txt
diff --git a/doc/server/configuration.txt b/doc/server/configuration.txt
index d3fa42601..79d732f6d 100644
--- a/doc/server/configuration.txt
+++ b/doc/server/configuration.txt
@@ -216,3 +216,46 @@ To select which backend to use, set the ``backend`` option in the
* ``best`` (the default; currently the same as ``builtin``)
``best`` may change in future releases.
+
+Multiprocessing core configuration
+----------------------------------
+
+If you use the multiprocessing core, there are other bits you may wish
+to twiddle.
+
+By default, the server spawns as many children as the host has CPUs.
+(This is determined by ``multiprocessing.cpu_count()``.) To change
+this, set:
+
+.. code-block:: ini
+
+ [server]
+ children = 4
+
+The optimal number of children may vary depending on your workload.
+For instance, if you are using :ref:`native yum
+library support <native-yum-libraries>`, then a separate process is
+spawned for each client to resolve its package dependencies, so
+keeping the children at or below the CPU count is likely a good idea.
+If you're not using native yum library support, though, you may wish
+to oversubscribe the core slightly. It's recommended that you test
+various configurations and use what works best for your workload.
+
+Secondly, if ``tmpwatch`` is enabled, you must either disable it or
+exclude the pattern ``/tmp/pymp-\*``. For instance, on RHEL or CentOS
+you may have a line like the following in
+``/etc/cron.daily/tmpwatch``:
+
+.. code-block:: bash
+
+ /usr/sbin/tmpwatch -x /tmp/.X11-unix -x /tmp/.XIM-unix -x /tmp/.font-unix \
+ -x /tmp/.ICE-unix -x /tmp/.Test-unix 240 /tmp
+
+You would need to add ``-X /tmp/pymp-\*`` to it, like so:
+
+.. code-block:: bash
+
+ /usr/sbin/tmpwatch -x /tmp/.X11-unix -x /tmp/.XIM-unix -x /tmp/.font-unix \
+ -x /tmp/.ICE-unix -x /tmp/.Test-unix -X /tmp/pymp-\* 240 /tmp
+
+See https://bugzilla.redhat.com/show_bug.cgi?id=1058310 for more information.
diff --git a/doc/server/plugins/generators/rules.txt b/doc/server/plugins/generators/rules.txt
index c5ff699a7..86478a5ae 100644
--- a/doc/server/plugins/generators/rules.txt
+++ b/doc/server/plugins/generators/rules.txt
@@ -277,6 +277,7 @@ child ``<ACL>`` tags. For instance:
mode="0775">
<ACL type="default" scope="user" user="foouser" perms="rw"/>
<ACL type="default" scope="group" group="users" perms="rx"/>
+ <ACL type="default" scope="other" perms="r"/>
</Path>
.. xml:element:: ACL
@@ -285,6 +286,9 @@ It is not currently possible to manually set an effective rights mask;
the mask will be automatically calculated from the given ACLs when
they are applied.
+For directories either no default ACL entries or at least an entry for
+the owner, owning group and other must be defined.
+
Note that it is possible to set ACLs that demand different permissions
on a file than those specified in the ``perms`` attribute on the
``Path`` tag. For instance:
diff --git a/doc/server/plugins/generators/sshbase.txt b/doc/server/plugins/generators/sshbase.txt
index 641b9c598..4578d5810 100644
--- a/doc/server/plugins/generators/sshbase.txt
+++ b/doc/server/plugins/generators/sshbase.txt
@@ -1,4 +1,5 @@
.. -*- mode: rst -*-
+.. vim: ft=rst
.. _server-plugins-generators-sshbase:
@@ -13,8 +14,9 @@ record for the current system.
It has two functions:
-* Generating new ssh keys -- When a client requests a dsa, rsa, or v1 key,
- and there is no existing key in the repository, one is generated.
+* 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
+ generated.
* Maintaining the ``ssh_known_hosts`` file -- all current known public
keys (and extra public key stores) are integrated into a single
@@ -31,7 +33,7 @@ Interacting with SSHbase
``<repo>/SSHbase/<key filename>.H_<hostname>``
* Pre-seeding can also be performed using ``bcfg2-admin pull
- ConfigFile /name/of/ssh/key``
+ Path /name/of/ssh/key``
* Revoking existing keys -- deleting
``<repo>/SSHbase/\*.H_<hostname>`` will remove keys for an existing
diff --git a/doc/unsorted/emacs_snippet.txt b/doc/unsorted/emacs_snippet.txt
index b9f7fd25b..4eefb4583 100644
--- a/doc/unsorted/emacs_snippet.txt
+++ b/doc/unsorted/emacs_snippet.txt
@@ -31,7 +31,7 @@ More snippets are under development.
("<Group" "<Group name='${1:groupname}>
$0
</Group>" nil)
- ("<Config" "<ConfigFile name='${1:filename}'/>
+ ("<Path" "<Path name='${1:filename}'/>
$0" nil)
("<Service" "<Service name='${1:svcname}'/>
$0" nil)
diff --git a/doc/unsorted/vim_snippet.txt b/doc/unsorted/vim_snippet.txt
index e4fda7eca..4598b5c1d 100644
--- a/doc/unsorted/vim_snippet.txt
+++ b/doc/unsorted/vim_snippet.txt
@@ -30,9 +30,9 @@ that allow quick composition of bundles and base files.
<Group name='${1:groupname}'>
${2}
</Group>
- # ConfigFile
- snippet <Config
- <ConfigFile name='${1:filename}'/>
+ # Path
+ snippet <Path
+ <Path name='${1:filename}'/>
# Service
snippet <Service
<Service name='${1:svcname}'/>
diff --git a/misc/bcfg2-selinux.spec b/misc/bcfg2-selinux.spec
index d694783b5..d33953e08 100644
--- a/misc/bcfg2-selinux.spec
+++ b/misc/bcfg2-selinux.spec
@@ -16,7 +16,7 @@
%global _pre_rc %{?_pre:.pre%{_pre}}%{?_rc:.rc%{_rc}}
Name: bcfg2-selinux
-Version: 1.3.3
+Version: 1.3.4
Release: 1%{?_pre_rc}%{?dist}
Summary: Bcfg2 Client and Server SELinux policy
@@ -32,7 +32,7 @@ Conflicts: selinux-policy = 3.11.1
License: BSD
URL: http://bcfg2.org
-Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%{name}-%{version}.tar.gz
+Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%{name}-%{version}%{?_pre_rc}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec
index 0ce51b207..ab1394110 100644
--- a/misc/bcfg2.spec
+++ b/misc/bcfg2.spec
@@ -24,6 +24,12 @@
%global _date %(date +%Y%m%d)
%global _pre_rc %{?_pre:pre%{_pre}}%{?_rc:rc%{_rc}}
+# cherrypy 3.3 actually doesn't exist yet, but 3.2 has bugs that
+# prevent it from working:
+# https://bitbucket.org/cherrypy/cherrypy/issue/1154/assertionerror-in-recv-when-ssl-is-enabled
+%global build_cherry_py 0
+
+
Name: bcfg2
Version: 1.4.0
Release: 0.1.%{?_nightly:nightly.%{_date}}%{?_pre_rc}%{?dist}
@@ -56,7 +62,9 @@ BuildRequires: python-Genshi
BuildRequires: python-gamin
BuildRequires: python-pyinotify
BuildRequires: python-python-daemon
+%if %{build_cherry_py}
BuildRequires: python-CherryPy >= 3
+%endif
%else # ! suse_version
BuildRequires: python-daemon
BuildRequires: python-inotify
@@ -73,18 +81,30 @@ BuildRequires: python-ssl
BuildRequires: python-nose
BuildRequires: mock
BuildRequires: m2crypto
+# EPEL uses the properly-named python-django starting with EPEL7
+%if 0%{?rhel} && 0%{?rhel} > 6
+BuildRequires: python-django
+%else
BuildRequires: Django
+%endif
BuildRequires: python-genshi
BuildRequires: python-cheetah
-BuildRequires: pylibacl
BuildRequires: libselinux-python
+BuildRequires: pylibacl
BuildRequires: python-pep8
+BuildRequires: pylint
+%if %{build_cherry_py}
BuildRequires: python-cherrypy >= 3
+%endif
BuildRequires: python-mock
-BuildRequires: pylint
%endif # rhel > 5
%endif # vendor != redhat || rhel defined
%endif # ! suse_version
+%if 0%{?fedora} && 0%{?fedora} >= 16 || 0%{?rhel} && 0%{?rhel} >= 7
+# Pick up _unitdir macro
+BuildRequires: systemd
+%endif
+
%if 0%{?mandriva_version}
# mandriva seems to behave differently than other distros and needs
@@ -227,6 +247,7 @@ deployment strategies.
This package includes the Bcfg2 server software.
+%if %{build_cherry_py}
%package server-cherrypy
Summary: Bcfg2 Server - CherryPy backend
%if 0%{?suse_version}
@@ -269,6 +290,8 @@ Bcfg2 can enable the construction of complex change management and
deployment strategies.
This package includes the Bcfg2 CherryPy server backend.
+%endif # build_cherry_py
+
%package web
Summary: Bcfg2 Web Reporting Interface
@@ -281,9 +304,15 @@ Requires: python-django >= 1.2
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
+%else
Requires: Django >= 1.2
Requires: Django-south >= 0.7
%endif
+Requires: bcfg2-server
+%endif
%if "%{_vendor}" == "redhat"
Requires: mod_wsgi
%global apache_conf %{_sysconfdir}/httpd
@@ -450,7 +479,7 @@ install -d %{buildroot}/var/adm/fillup-templates
mv %{buildroot}%{_bindir}/bcfg2* %{buildroot}%{_sbindir}
-%if 0%{?fedora} < 16
+%if 0%{?fedora} && 0%{?fedora} < 16 || 0%{?rhel} && 0%{?rhel} < 7
# Install SysV init scripts for everyone but new Fedoras
install -m 755 redhat/scripts/bcfg2.init \
%{buildroot}%{_initrddir}/bcfg2
@@ -488,10 +517,17 @@ install -p -m 644 redhat/systemd/%{name}.service \
install -p -m 644 redhat/systemd/%{name}-server.service \
%{buildroot}%{_unitdir}/%{name}-server.service
+%if 0%{?rhel} != 5
# Webserver
install -d %{buildroot}%{apache_conf}/conf.d
install -p -m 644 misc/apache/bcfg2.conf \
%{buildroot}%{apache_conf}/conf.d/wsgi_bcfg2.conf
+%else
+# remove web server files not in EL5 packages
+rm -r %{buildroot}%{_datadir}/bcfg2/reports.wsgi \
+ %{buildroot}%{_datadir}/bcfg2/site_media
+%endif
+
# mandriva cannot handle %ghost without the file existing,
# so let's touch a bunch of empty config files
@@ -662,7 +698,7 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
%{_mandir}/man5/bcfg2.conf.5*
%ghost %attr(600,root,root) %config(noreplace,missingok) %{_sysconfdir}/bcfg2.cert
%ghost %attr(0600,root,root) %config(noreplace,missingok) %{_sysconfdir}/bcfg2.conf
-%if 0%{?fedora} >= 16
+%if 0%{?fedora} >= 16 || 0%{?rhel} >= 7
%config(noreplace) %{_unitdir}/%{name}.service
%else
%{_initrddir}/bcfg2
@@ -696,7 +732,7 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
%defattr(-,root,root,-)
%endif
%ghost %attr(600,root,root) %config(noreplace) %{_sysconfdir}/bcfg2.key
-%if 0%{?fedora} >= 16
+%if 0%{?fedora} >= 16 || 0%{?rhel} >= 7
%config(noreplace) %{_unitdir}/%{name}-server.service
%else
%{_initrddir}/bcfg2-server
@@ -709,7 +745,7 @@ 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*
-%exclude %{python_sitelib}/Bcfg2/Server/CherrypyCore.py
+%exclude %{python_sitelib}/Bcfg2/Server/CherryPyCore.py*
%dir %{_datadir}/bcfg2
%{_datadir}/bcfg2/schemas
@@ -724,19 +760,24 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
%doc tools/*
+%if %{build_cherry_py}
%files server-cherrypy
%if 0%{?rhel} == 5 || 0%{?suse_version}
%defattr(-,root,root,-)
%endif
-%{python_sitelib}/Bcfg2/Server/CherrypyCore.py
+%{python_sitelib}/Bcfg2/Server/CherryPyCore.py
+%endif
+# bcfg2-web package is disabled on EL5, which lacks Django
+%if 0%{?rhel} != 5
%files web
-%if 0%{?rhel} == 5 || 0%{?suse_version}
+%if 0%{?suse_version}
%defattr(-,root,root,-)
%endif
%{_datadir}/bcfg2/reports.wsgi
%{_datadir}/bcfg2/site_media
%config(noreplace) %{apache_conf}/conf.d/wsgi_bcfg2.conf
+%endif
%files doc
%if 0%{?rhel} == 5 || 0%{?suse_version}
@@ -752,6 +793,31 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
%changelog
+* Sun Apr 6 2014 John Morris <john@zultron.com> - 1.3.4-1
+- New upstream release
+
+* Wed Feb 26 2014 John Morris <john@zultron.com> - 1.3.3-5
+- EL7: Re-add deps and re-enable %%check script; bz #1058427
+
+* Sat Feb 1 2014 John Morris <john@zultron.com> - 1.3.3-4
+- Disable bcfg2-web package on EL5; bz #1058427
+- Disable %%check on EL7; missing EPEL deps
+- BR: systemd to pick up _unitdir macro
+
+* Mon Jan 27 2014 Sol Jerome <sol.jerome@gmail.com> - 1.3.3-4
+- Fix BuildRequires for EPEL7's Django
+- Remove unnecessary client-side lxml dependency
+- Add Django dependency for bcfg2-web (the web package *does* require
+ Django for the database)
+- Fix OS detection for RHEL7 initscripts
+
+* Sun Dec 15 2013 John Morris <john@zultron.com> - 1.3.3-3
+- Remove unneeded Django dep in 'web' package, bz #1043229
+
+* Sun Nov 24 2013 John Morris <john@zultron.com> - 1.3.3-2
+- Fix CherryPyCore.py exclude glob to include compiled files
+- Disable server-cherrypy package build to make Fedora buildsys happy
+
* Thu Nov 07 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.3-1
- New upstream release
@@ -773,23 +839,32 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
- Changes to %%post* scripts
- Rearrange %%files sections
-* Mon Jul 01 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.2-1
-- New upstream release
-
-* Thu Mar 21 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.1-1
-- New upstream release
-
-* Fri Mar 15 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.0-0.0
-- New upstream release
-
-* Tue Jan 29 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.0-0.0rc2
-- New upstream release
-
-* Wed Jan 09 2013 Sol Jerome <sol.jerome@gmail.com> 1.3.0-0.0rc1
-- New upstream release
-
-* Tue Oct 30 2012 Sol Jerome <sol.jerome@gmail.com> 1.3.0-0.0pre2
-- New upstream release
+* Wed Jul 3 2013 John Morris <john@zultron.com> - 1.3.2-1
+- Update to new upstream version 1.3.2
+- Move settings.py into server package (fixes bug reported on bcfg2-dev ML)
+- Use init scripts from redhat/scripts directory
+- Fix EL5/EL6 sphinx docs
+- Require python-inotify instead of gamin-python; recommended by upstream
+- Remove obsolete bcfg2-py27-auth.patch, accepted upstream
+- Add %%check script
+ - Hack test suite to use local copies of XMLSchema.xsd and xml.xsd
+ - Many new BRs to support %%check script
+ - Disable %%check script on EL5, where there is no python-mock package
+- Cleanups to _pre/_rc macros
+- Mark EL5 relics
+- Other minor formatting
+
+* Mon Apr 08 2013 Fabian Affolter <mail@fabian-affolter.ch> - 1.3.1-1
+- Updated to new upstream version 1.3.1
+
+* Mon Mar 18 2013 Fabian Affolter <mail@fabian-affolter.ch> - 1.3.0-1
+- Updated to new upstream version 1.3.0
+
+* Wed Feb 13 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.3.0-0.2.pre2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
+* Wed Oct 31 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.3.0-0.1.pre2
+- Updated to new upstream version 1.3.0 pre2
* Wed Oct 17 2012 Chris St. Pierre <chris.a.st.pierre@gmail.com> 1.3.0-0.2pre1
- Split bcfg2-selinux into its own specfile
@@ -797,12 +872,28 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
* Fri Sep 14 2012 Chris St. Pierre <chris.a.st.pierre@gmail.com> 1.3.0-0.1pre1
- Added -selinux subpackage
-* Fri Aug 31 2012 Sol Jerome <sol.jerome@gmail.com> 1.3.0-0.0pre1
-- New upstream release
+* Mon Aug 27 2012 Václav Pavlín <vpavlin@redhat.com> - 1.2.3-3
+- Scriptlets replaced with new systemd macros (#850043)
* Wed Aug 15 2012 Chris St. Pierre <chris.a.st.pierre@gmail.com> 1.2.3-0.1
- Added tools/ as doc for bcfg2-server subpackage
+* Wed Jul 18 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.2.3-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
+
+* Sat Jul 07 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.3-1
+- Fix CVE-2012-3366
+- Updated to new upstream version 1.2.3
+
+* Tue May 01 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.2-2
+- python-nose is needed by bcfg2-test
+
+* Fri Apr 06 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.2-1
+- Updated to new upstream version 1.2.2
+
+* Sun Feb 26 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.1-2
+- Fixed systemd files
+
* Sat Feb 18 2012 Christopher 'm4z' Holm <686f6c6d@googlemail.com> 1.2.1
- Added Fedora and Mandriva compatibilty (for Open Build Service).
- Added missing dependency redhat-lsb.
@@ -811,15 +902,212 @@ sed "s@http://www.w3.org/2001/xml.xsd@file://$(pwd)/schemas/xml.xsd@" \
- Added openSUSE compatibility.
- Various changes to satisfy rpmlint.
+* Tue Feb 07 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.1-1
+- Added examples package
+- Updated to new upstream version 1.2.1
+
+* Mon Jan 02 2012 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-6
+- Added support for systemd
+- Example subpackage
+
+* Wed Sep 07 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-5
+- Updated to new upstreadm version 1.2.0
+
+* Wed Sep 07 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-4.1.rc1
+- Updated to new upstreadm version 1.2.0rc1
+
+* Wed Jun 22 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-3.1.pre3
+- Updated to new upstreadm version 1.2.0pre3
+
+* Wed May 04 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-2.1.pre2
+- Added bcfg2-lint stuff
+- Pooled file section entries to reduce future maintainance
+- Removed Patch
+
+* Wed May 04 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-1.1.pre2
+- Updated to new upstream version 1.2.0pre2
+
+* Sun Mar 20 2011 Fabian Affolter <mail@fabian-affolter.ch> - 1.2.0-1.1.pre1
+- Added doc subpackage
+- Updated to new upstream version 1.2.0pre1
+
+* Mon Feb 07 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.1.1-2.1
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
+
* Thu Jan 27 2011 Chris St. Pierre <chris.a.st.pierre@gmail.com> 1.2.0pre1-0.0
- Added -doc sub-package
+* Thu Nov 18 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.1-2
+- Added new man page
+- Updated doc section (ChangeLog is gone)
+
+* Thu Nov 18 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.1-1
+- Updated to new upstream version 1.1.1
+
+* Fri Nov 5 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.1.0-3
+- Add patch from Gordon Messmer to fix authentication on F14+ (Python 2.7)
+
+* Mon Sep 27 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.1.0-2
+- Update to final version
+
+* Wed Sep 15 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.1.0-1.3.rc5
+- Update to 1.1.0rc5:
+
+* Tue Aug 31 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.1.0-1.2.rc4
+- Add new YUMng driver
+
+* Wed Jul 21 2010 David Malcolm <dmalcolm@redhat.com> - 1.1.0-1.1.rc4.1
+- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild
+
+* Tue Jul 20 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.0-1.1.rc4
+- Added patch to fix indention
+
+* Tue Jul 20 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.0-0.1.rc4
+- Updated to new upstream release candidate RC4
+
* Mon Jun 21 2010 Fabian Affolter <fabian@bernewireless.net> - 1.1.0rc3-0.1
- Changed source0 in order that it works with spectool
+* Sat Jun 19 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.0-0.1.rc3
+- Updated to new upstream release candidate RC3
+
+* Sun May 02 2010 Fabian Affolter <mail@fabian-affolter.ch> - 1.1.0-0.2.rc1
+- Changed define to global
+- Added graphviz for the server package
+
+* Wed Apr 28 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.1.0-0.1.rc1
+- Update to 1.1.0rc1
+
+* Tue Apr 13 2010 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.1-1
+- Update to final version
+
+* Fri Nov 6 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.0-2
+- Fixup the bcfg2-server init script
+
+* Fri Nov 6 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.0-1
+- Update to 1.0.0 final
+
+* Wed Nov 4 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.0-0.5.rc2
+- Only require python-ssl on EPEL
+
+* Sat Oct 31 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.0-0.4.rc2
+- Update to 1.0.0rc2
+
+* Mon Oct 26 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0.0-0.3.rc1
+- Update to 1.0rc1
+
+* Fri Oct 16 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0-0.2.pre5
+- Add python-ssl requirement
+
+* Tue Aug 11 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 1.0-0.1.pre5
+- Update to 1.0pre5
+
+* Fri Jul 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.6-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild
+
+* Mon Feb 23 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.6-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild
+
+* Sat Nov 29 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm@gmail.com> - 0.9.6-2
+- Rebuild for Python 2.6
+
+* Tue Nov 18 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.6-1
+- Update to 0.9.6 final.
+
+* Tue Oct 14 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.6-0.8.pre3
+- Update to 0.9.6pre3
+
+* Sat Aug 9 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.6-0.2.pre2
+- Update to 0.9.6pre2
+
+* Wed May 28 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.6-0.1.pre1
+- Update to 0.9.6pre1
+
+* Fri Feb 15 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.7-1
+- Update to 0.9.5.7.
+
+* Fri Feb 15 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.7-1
+- Update to 0.9.5.7.
+
+* Fri Jan 11 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.5-1
+- Update to 0.9.5.5
+- More egg-info entries.
+
+* Wed Jan 9 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.4-1
+- Update to 0.9.5.4.
+
+* Tue Jan 8 2008 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.3-1
+- Update to 0.9.5.3
+- Package egg-info files.
+
+* Mon Nov 12 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5.2-1
+- Update to 0.9.5.2
+
+* Mon Nov 12 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5-2
+- Fix oops.
+
+* Mon Nov 12 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5-1
+- Update to 0.9.5 final.
+
+* Mon Nov 05 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5-0.5.pre7
+- Commit new patches to CVS.
+
+* Mon Nov 05 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.5-0.4.pre7
+- Update to 0.9.5pre7
+
+* Wed Jun 27 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-4
+- Oops, apply right patch
+
+* Wed Jun 27 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-3
+- Add patch to fix YUMng problem
+
+* Mon Jun 25 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-2
+- Bump revision and rebuild
+
+* Mon Jun 25 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-1
+- Update to 0.9.4 final
+
+* Thu Jun 21 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-0.1.pre4
+- Update to 0.9.4pre4
+
+* Thu Jun 14 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-0.1.pre3
+- Update to 0.9.4pre3
+
+* Tue Jun 12 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.4-0.1.pre2
+- Update to 0.9.4pre2
+
+* Tue May 22 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.3-2
+- Drop requires on pyOpenSSL
+- Add requires on redhat-lsb
+- (Fixes #240871)
+
+* Mon Apr 30 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.3-1
+- Update to 0.9.3
+
+* Tue Mar 20 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.2-4
+- Server needs pyOpenSSL
+
+* Wed Feb 28 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.2-3
+- Don't forget %%dir
+
+* Wed Feb 28 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.2-2
+- Fix #230478
+
+* Mon Feb 19 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.2-1
+- Update to 0.9.2
+
+* Thu Feb 8 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.9.1-1.d
+- Update to 0.9.1d
+
* Fri Feb 2 2007 Mike Brady <mike.brady@devnull.net.nz> 0.9.1
- Removed use of _libdir due to Red Hat x86_64 issue.
+* Tue Jan 9 2007 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.8.7.3-2
+- Merge client back into base package.
+
+* Wed Dec 27 2006 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.8.7.3-1
+- Update to 0.8.7.3
+
* Fri Dec 22 2006 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.8.7.1-5
- Server needs client library files too so put them in main package
diff --git a/osx/Makefile b/osx/Makefile
index 7714fa4d8..3cc92f9dc 100644
--- a/osx/Makefile
+++ b/osx/Makefile
@@ -28,9 +28,9 @@ SITELIBDIR = /Library/Python/${PYVERSION}/site-packages
# an Info.plist file for packagemaker to look at for package creation
# and substitute the version strings. Major/Minor versions can only be
# integers (e.g. "1" and "00" for bcfg2 version 1.0.0.
-BCFGVER = 1.3.3
+BCFGVER = 1.3.4
MAJOR = 1
-MINOR = 33
+MINOR = 34
default: clean client
diff --git a/osx/macports/Portfile b/osx/macports/Portfile
index 83c7f4075..28e345c3b 100644
--- a/osx/macports/Portfile
+++ b/osx/macports/Portfile
@@ -5,7 +5,7 @@ PortSystem 1.0
PortGroup python26 1.0
name bcfg2
-version 1.3.3
+version 1.3.4
categories sysutils python
maintainers gmail.com:sol.jerome
license BSD
diff --git a/redhat/scripts/bcfg2-server.init b/redhat/scripts/bcfg2-server.init
index c4412d1c3..7fd1bd906 100755
--- a/redhat/scripts/bcfg2-server.init
+++ b/redhat/scripts/bcfg2-server.init
@@ -49,7 +49,13 @@ start () {
stop () {
echo -n $"Stopping $prog: "
- killproc ${prog} && success || failure
+ # we do NOT want to specify the pidfile to killproc; if we do, and
+ # it has to kill -9 the server, it only kills the master and the
+ # child processes stay running (if the multiprocessing core is in
+ # use). By not specifying a pidfile, it looks in the process
+ # table for all bcfg2-server processes, and kill -9's them all if
+ # necessary.
+ killproc -d 30 ${prog} && success || failure
RETVAL=$?
echo
rm -f /var/lock/subsys/$prog
diff --git a/redhat/systemd/bcfg2.service b/redhat/systemd/bcfg2.service
index 6cbad2e5a..245c80cce 100644
--- a/redhat/systemd/bcfg2.service
+++ b/redhat/systemd/bcfg2.service
@@ -3,11 +3,12 @@ Description=Bcfg2 configuration client
After=syslog.target network.target
[Service]
-Type=forking
+Type=oneshot
StandardOutput=syslog
StandardError=syslog
EnvironmentFile=-/etc/sysconfig/bcfg2
ExecStart=/usr/sbin/bcfg2 $BCFG2_OPTIONS
+RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
diff --git a/schemas/augeas.xsd b/schemas/augeas.xsd
index 0ede106f3..df27f91cc 100644
--- a/schemas/augeas.xsd
+++ b/schemas/augeas.xsd
@@ -173,6 +173,15 @@
</xsd:documentation>
</xsd:annotation>
<xsd:choice>
+ <xsd:element name="Initial" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>
+ Specify initial content for a file, which will be created
+ before Augeas commands are applied if a file doesn't
+ exist.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
<xsd:element name="Remove" type="AugeasRemoveCommand">
<xsd:annotation>
<xsd:documentation>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index 9864730ea..a0fb7ed0a 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -193,6 +193,7 @@
<xsd:restriction base="xsd:string">
<xsd:enumeration value="user"/>
<xsd:enumeration value="group"/>
+ <xsd:enumeration value="other"/>
</xsd:restriction>
</xsd:simpleType>
diff --git a/solaris-ips/MANIFEST.bcfg2-server.header b/solaris-ips/MANIFEST.bcfg2-server.header
index 59929fcfa..b72adc486 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.3.3"
+set name=pkg.fmri value="pkg://bcfg2/bcfg2-server@1.3.4"
diff --git a/solaris-ips/MANIFEST.bcfg2.header b/solaris-ips/MANIFEST.bcfg2.header
index 5f48a60a1..19fc567fe 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.3.3"
+set name=pkg.fmri value="pkg://bcfg2/bcfg2@1.3.4"
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 71523f48e..7d59e2456 100644
--- a/solaris-ips/Makefile
+++ b/solaris-ips/Makefile
@@ -1,6 +1,6 @@
#!/usr/bin/gmake
-VERS=1.3.3-1
+VERS=1.3.4-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 00483f961..80f26fc0a 100644
--- a/solaris-ips/pkginfo.bcfg2
+++ b/solaris-ips/pkginfo.bcfg2
@@ -1,7 +1,7 @@
PKG="SCbcfg2"
NAME="bcfg2"
ARCH="sparc"
-VERSION="1.3.3"
+VERSION="1.3.4"
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 ecc5e72c1..88d0e6dff 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.3.3"
+VERSION="1.3.4"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/solaris/Makefile b/solaris/Makefile
index 3b367ef71..995253ea8 100644
--- a/solaris/Makefile
+++ b/solaris/Makefile
@@ -1,7 +1,7 @@
#!/usr/sfw/bin/gmake
PYTHON="/usr/local/bin/python"
-VERS=1.3.3-1
+VERS=1.3.4-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 00483f961..80f26fc0a 100644
--- a/solaris/pkginfo.bcfg2
+++ b/solaris/pkginfo.bcfg2
@@ -1,7 +1,7 @@
PKG="SCbcfg2"
NAME="bcfg2"
ARCH="sparc"
-VERSION="1.3.3"
+VERSION="1.3.4"
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 ecc5e72c1..88d0e6dff 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.3.3"
+VERSION="1.3.4"
CATEGORY="application"
VENDOR="Argonne National Labratory"
EMAIL="bcfg-dev@mcs.anl.gov"
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
index 4f6953b2a..dc5fc6755 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py
@@ -4,6 +4,7 @@ import sys
import Bcfg2.Client.XML
from augeas import Augeas
from Bcfg2.Client.Tools.POSIX.base import POSIXTool
+from Bcfg2.Client.Tools.POSIX.File import POSIXFile
class AugeasCommand(object):
@@ -187,13 +188,14 @@ class Insert(AugeasCommand):
class POSIXAugeas(POSIXTool):
""" Handle <Path type='augeas'...> entries. See
:ref:`client-tools-augeas`. """
-
- __handles__ = [('Path', 'augeas')]
- __req__ = {'Path': ['type', 'name', 'setting', 'value']}
+ __req__ = ['name', 'mode', 'owner', 'group']
def __init__(self, config):
POSIXTool.__init__(self, config)
self._augeas = dict()
+ # file tool for setting initial values of files that don't
+ # exist
+ self.filetool = POSIXFile(logger, setup, config)
def get_augeas(self, entry):
""" Get an augeas object for the given entry. """
@@ -214,9 +216,9 @@ class POSIXAugeas(POSIXTool):
return self._augeas[entry.get("name")]
def fully_specified(self, entry):
- return entry.text is not None
+ return len(entry.getchildren()) != 0
- def get_commands(self, entry, unverified=False):
+ def get_commands(self, entry):
""" Get a list of commands to verify or install.
@param entry: The entry to get commands from.
@@ -229,7 +231,7 @@ class POSIXAugeas(POSIXTool):
"""
rv = []
for cmd in entry.iterchildren():
- if unverified and cmd.get("verified", "false") != "false":
+ if cmd.tag == "Initial":
continue
if cmd.tag in globals():
rv.append(globals()[cmd.tag](cmd, self.get_augeas(entry),
@@ -266,7 +268,18 @@ class POSIXAugeas(POSIXTool):
def install(self, entry):
rv = True
- for cmd in self.get_commands(entry, unverified=True):
+ if entry.get("current_exists", "true") == "false":
+ initial = entry.find("Initial")
+ if initial is not None:
+ self.logger.debug("Augeas: Setting initial data for %s" %
+ entry.get("name"))
+ file_entry = Bcfg2.Client.XML.Element("Path",
+ **dict(entry.attrib))
+ file_entry.text = initial.text
+ self.filetool.install(file_entry)
+ # re-parse the file
+ self.get_augeas(entry).load()
+ for cmd in self.get_commands(entry):
try:
cmd.install()
except: # pylint: disable=W0702
@@ -277,8 +290,7 @@ class POSIXAugeas(POSIXTool):
try:
self.get_augeas(entry).save()
except: # pylint: disable=W0702
- self.logger.error(
- "Failure saving Augeas changes to %s: %s" %
- (entry.get("name"), sys.exc_info()[1]))
+ self.logger.error("Failure saving Augeas changes to %s: %s" %
+ (entry.get("name"), sys.exc_info()[1]))
rv = False
return POSIXTool.install(self, entry) and rv
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
index 712620206..8895eaae1 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/base.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -217,18 +217,13 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
acl.delete_entry(aclentry)
if os.path.isdir(path):
defacl = posix1e.ACL(filedef=path)
- if not defacl.valid():
- # when a default ACL is queried on a directory that
- # has no default ACL entries at all, you get an empty
- # ACL, which is not valid. in this circumstance, we
- # just copy the access ACL to get a base valid ACL
- # that we can add things to.
- defacl = posix1e.ACL(acl=acl)
- else:
- for aclentry in defacl:
- if aclentry.tag_type in [posix1e.ACL_USER,
- posix1e.ACL_GROUP]:
- defacl.delete_entry(aclentry)
+ for aclentry in defacl:
+ if aclentry.tag_type in [posix1e.ACL_USER,
+ posix1e.ACL_USER_OBJ,
+ posix1e.ACL_GROUP,
+ posix1e.ACL_GROUP_OBJ,
+ posix1e.ACL_OTHER]:
+ defacl.delete_entry(aclentry)
else:
defacl = None
@@ -254,10 +249,16 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
try:
if scope == posix1e.ACL_USER:
scopename = "user"
- aclentry.qualifier = self._norm_uid(qualifier)
+ if qualifier:
+ aclentry.qualifier = self._norm_uid(qualifier)
+ else:
+ aclentry.tag_type = posix1e.ACL_USER_OBJ
elif scope == posix1e.ACL_GROUP:
scopename = "group"
- aclentry.qualifier = self._norm_gid(qualifier)
+ if qualifier:
+ aclentry.qualifier = self._norm_gid(qualifier)
+ else:
+ aclentry.tag_type = posix1e.ACL_GROUP_OBJ
except (OSError, KeyError):
err = sys.exc_info()[1]
self.logger.error("POSIX: Could not resolve %s %s: %s" %
@@ -358,7 +359,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
try:
# single octal digit
rv = int(perms)
- if rv > 0 and rv < 8:
+ if rv >= 0 and rv < 8:
return rv
else:
self.logger.error("POSIX: Permissions digit out of range in "
@@ -388,13 +389,17 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
""" Get a string representation of the given ACL. aclkey must
be a tuple of (<acl type>, <acl scope>, <qualifier>) """
atype, scope, qualifier = aclkey
+ if not qualifier:
+ qualifier = ''
acl_str = []
if atype == 'default':
acl_str.append(atype)
- if scope == posix1e.ACL_USER:
+ if scope == posix1e.ACL_USER or scope == posix1e.ACL_USER_OBJ:
acl_str.append("user")
- elif scope == posix1e.ACL_GROUP:
+ elif scope == posix1e.ACL_GROUP or scope == posix1e.ACL_GROUP_OBJ:
acl_str.append("group")
+ elif scope == posix1e.ACL_OTHER:
+ acl_str.append("other")
acl_str.append(qualifier)
acl_str.append(self._acl_perm2string(perms))
return ":".join(acl_str)
@@ -414,7 +419,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
""" Get data on the existing state of <path> -- e.g., whether
or not it exists, owner, group, permissions, etc. """
try:
- ondisk = os.stat(path)
+ ondisk = os.lstat(path)
except OSError:
self.logger.debug("POSIX: %s does not exist" % path)
return (False, None, None, None, None, None)
@@ -451,7 +456,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
if HAS_SELINUX:
try:
- secontext = selinux.getfilecon(path)[1].split(":")[2]
+ secontext = selinux.lgetfilecon(path)[1].split(":")[2]
except (OSError, KeyError):
err = sys.exc_info()[1]
self.logger.debug("POSIX: Could not get current SELinux "
@@ -460,7 +465,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
else:
secontext = None
- if HAS_ACLS:
+ if HAS_ACLS and not stat.S_ISLNK(ondisk[stat.ST_MODE]):
acls = self._list_file_acls(path)
else:
acls = None
@@ -562,9 +567,17 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
wanted = dict()
for acl in entry.findall("ACL"):
if acl.get("scope") == "user":
- scope = posix1e.ACL_USER
+ if acl.get("user"):
+ scope = posix1e.ACL_USER
+ else:
+ scope = posix1e.ACL_USER_OBJ
elif acl.get("scope") == "group":
- scope = posix1e.ACL_GROUP
+ if acl.get("group"):
+ scope = posix1e.ACL_GROUP
+ else:
+ scope = posix1e.ACL_GROUP_OBJ
+ elif acl.get("scope") == "other":
+ scope = posix1e.ACL_OTHER
else:
self.logger.error("POSIX: Unknown ACL scope %s" %
acl.get("scope"))
@@ -573,7 +586,10 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
self.logger.error("POSIX: No permissions set for ACL: %s" %
Bcfg2.Client.XML.tostring(acl))
continue
- wanted[(acl.get("type"), scope, acl.get(acl.get("scope")))] = \
+ qual = acl.get(acl.get("scope"))
+ if not qual:
+ qual = ''
+ wanted[(acl.get("type"), scope, qual)] = \
self._norm_acl_perms(acl.get('perms'))
return wanted
@@ -587,11 +603,12 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
""" Given an ACL object, process it appropriately and add
it to the return value """
try:
+ qual = ''
if acl.tag_type == posix1e.ACL_USER:
qual = pwd.getpwuid(acl.qualifier)[0]
elif acl.tag_type == posix1e.ACL_GROUP:
qual = grp.getgrgid(acl.qualifier)[0]
- else:
+ elif atype == "access" or acl.tag_type == posix1e.ACL_MASK:
return
except (OSError, KeyError):
err = sys.exc_info()[1]
@@ -621,9 +638,38 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
_process_acl(acl, "default")
return existing
- def _verify_acls(self, entry, path=None):
+ def _verify_acls(self, entry, path=None): # pylint: disable=R0912
""" verify POSIX ACLs on the given entry. return True if all
ACLS are correct, false otherwise """
+ def _verify_acl(aclkey, perms):
+ """ Given ACL data, process it appropriately and add it to
+ missing or wrong lists if appropriate """
+ if aclkey not in existing:
+ missing.append(self._acl2string(aclkey, perms))
+ elif existing[aclkey] != perms:
+ wrong.append((self._acl2string(aclkey, perms),
+ self._acl2string(aclkey, existing[aclkey])))
+ if path == entry.get("name"):
+ atype, scope, qual = aclkey
+ aclentry = Bcfg2.Client.XML.Element("ACL", type=atype,
+ perms=str(perms))
+ if (scope == posix1e.ACL_USER or
+ scope == posix1e.ACL_USER_OBJ):
+ aclentry.set("scope", "user")
+ elif (scope == posix1e.ACL_GROUP or
+ scope == posix1e.ACL_GROUP_OBJ):
+ aclentry.set("scope", "group")
+ elif scope == posix1e.ACL_OTHER:
+ aclentry.set("scope", "other")
+ else:
+ self.logger.debug("POSIX: Unknown ACL scope %s on %s" %
+ (scope, path))
+ return
+
+ if scope != posix1e.ACL_OTHER:
+ aclentry.set(aclentry.get("scope"), qual)
+ entry.append(aclentry)
+
if not HAS_ACLS:
if entry.findall("ACL"):
self.logger.debug("POSIX: ACLs listed for %s but no pylibacl "
@@ -644,25 +690,7 @@ class POSIXTool(Bcfg2.Client.Tools.Tool):
extra = []
wrong = []
for aclkey, perms in wanted.items():
- if aclkey not in existing:
- missing.append(self._acl2string(aclkey, perms))
- elif existing[aclkey] != perms:
- wrong.append((self._acl2string(aclkey, perms),
- self._acl2string(aclkey, existing[aclkey])))
- if path == entry.get("name"):
- atype, scope, qual = aclkey
- aclentry = Bcfg2.Client.XML.Element("ACL", type=atype,
- perms=str(perms))
- if scope == posix1e.ACL_USER:
- aclentry.set("scope", "user")
- elif scope == posix1e.ACL_GROUP:
- aclentry.set("scope", "group")
- else:
- self.logger.debug("POSIX: Unknown ACL scope %s on %s" %
- (scope, path))
- continue
- aclentry.set(aclentry.get("scope"), qual)
- entry.append(aclentry)
+ _verify_acl(aclkey, perms)
for aclkey, perms in existing.items():
if aclkey not in wanted:
diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
index 58a3bbdfc..1a3b22506 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py
@@ -160,7 +160,8 @@ class POSIXUsers(Bcfg2.Client.Tools.Tool):
""" Get a list of supplmentary groups that the user in the
given entry is a member of """
return [g for g in self.existing['POSIXGroup'].values()
- if entry.get("name") in g[3] and g[0] != entry.get("group")]
+ if entry.get("name") in g[3] and g[0] != entry.get("group")
+ and self._in_managed_range('POSIXGroup', g[2])]
def VerifyPOSIXUser(self, entry, _):
""" Verify a POSIXUser entry """
diff --git a/src/lib/Bcfg2/Client/Tools/Systemd.py b/src/lib/Bcfg2/Client/Tools/Systemd.py
index 20a172d3d..027d91c71 100644
--- a/src/lib/Bcfg2/Client/Tools/Systemd.py
+++ b/src/lib/Bcfg2/Client/Tools/Systemd.py
@@ -13,8 +13,6 @@ class Systemd(Bcfg2.Client.Tools.SvcTool):
__handles__ = [('Service', 'systemd')]
__req__ = {'Service': ['name', 'status']}
- conflicts = ['Chkconfig']
-
def get_svc_command(self, service, action):
return "/bin/systemctl %s %s.service" % (action, service.get('name'))
diff --git a/src/lib/Bcfg2/Client/Tools/VCS.py b/src/lib/Bcfg2/Client/Tools/VCS.py
index 4e8ac76a4..449503b55 100644
--- a/src/lib/Bcfg2/Client/Tools/VCS.py
+++ b/src/lib/Bcfg2/Client/Tools/VCS.py
@@ -165,12 +165,13 @@ class VCS(Bcfg2.Client.Tools.Tool):
def Verifysvn(self, entry, _):
"""Verify svn repositories"""
+ # pylint: disable=E1101
headrev = pysvn.Revision(pysvn.opt_revision_kind.head)
+ # pylint: enable=E1101
client = pysvn.Client()
try:
cur_rev = str(client.info(entry.get('name')).revision.number)
- server = client.info2(entry.get('sourceurl'),
- headrev,
+ server = client.info2(entry.get('sourceurl'), headrev,
recurse=False)
if server:
server_rev = str(server[0][1].rev.number)
diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py
index 8bb87540c..7782581c1 100644
--- a/src/lib/Bcfg2/Client/Tools/YUM.py
+++ b/src/lib/Bcfg2/Client/Tools/YUM.py
@@ -612,34 +612,38 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
package_fail = True
stat['version_fail'] = True
# Just chose the first pkg for the error message
+ current_pkg = all_pkg_objs[0]
if virt_pkg:
provides = \
- [p for p in all_pkg_objs[0].provides
+ [p for p in current_pkg.provides
if p[0] == entry.get("name")][0]
- entry.set('current_version', "%s:%s-%s" % provides[2])
+ current_evr = provides[2]
self.logger.info(
" %s: Wrong version installed. "
"Want %s, but %s provides %s" %
(entry.get("name"),
nevra2string(nevra),
- nevra2string(all_pkg_objs[0]),
+ nevra2string(current_pkg),
yum.misc.prco_tuple_to_string(provides)))
else:
- entry.set('current_version', "%s:%s-%s.%s" %
- (all_pkg_objs[0].epoch,
- all_pkg_objs[0].version,
- all_pkg_objs[0].release,
- all_pkg_objs[0].arch))
+ current_evr = (current_pkg.epoch,
+ current_pkg.version,
+ current_pkg.release)
self.logger.info(" %s: Wrong version installed. "
"Want %s, but have %s" %
(entry.get("name"),
nevra2string(nevra),
- nevra2string(all_pkg_objs[0])))
- entry.set('version', "%s:%s-%s.%s" %
- (nevra.get('epoch', 'any'),
- nevra.get('version', 'any'),
- nevra.get('release', 'any'),
- nevra.get('arch', 'any')))
+ nevra2string(current_pkg)))
+ wanted_evr = (nevra.get('epoch', 'any'),
+ nevra.get('version', 'any'),
+ nevra.get('release', 'any'))
+ entry.set('current_version', "%s:%s-%s" % current_evr)
+ entry.set('version', "%s:%s-%s" % wanted_evr)
+ if yum.compareEVR(current_evr, wanted_evr) == 1:
+ entry.set("package_fail_action", "downgrade")
+ else:
+ entry.set("package_fail_action", "update")
+
qtext_versions.append("U(%s)" % str(all_pkg_objs[0]))
continue
@@ -912,6 +916,7 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
install_pkgs = []
gpg_keys = []
upgrade_pkgs = []
+ downgrade_pkgs = []
reinstall_pkgs = []
def queue_pkg(pkg, inst, queue):
@@ -953,11 +958,12 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
if (not status.get('installed', False) and
Bcfg2.Options.setup.yum_install_missing):
queue_pkg(pkg, inst, install_pkgs)
- elif (status.get('version_fail', False) and
- Bcfg2.Options.setup.yum_fix_version):
- queue_pkg(pkg, inst, upgrade_pkgs)
- elif (status.get('verify_fail', False) and
- Bcfg2.Options.setup.yum_reinstall_broken):
+ elif status.get('version_fail', False) and self.do_upgrade:
+ if pkg.get("package_fail_action") == "downgrade":
+ queue_pkg(pkg, inst, downgrade_pkgs)
+ else:
+ queue_pkg(pkg, inst, upgrade_pkgs)
+ elif status.get('verify_fail', False) and self.do_reinst:
queue_pkg(pkg, inst, reinstall_pkgs)
else:
# Either there was no Install/Version/Verify
@@ -1019,6 +1025,19 @@ class YUM(Bcfg2.Client.Tools.PkgTool):
self.logger.error("Error upgrading package %s: %s" %
(pkg_arg, yume))
+ if len(downgrade_pkgs) > 0:
+ self.logger.info("Attempting to downgrade packages")
+
+ for inst in downgrade_pkgs:
+ pkg_arg = self.instance_status[inst].get('pkg').get('name')
+ self.logger.debug("Downgrading %s" % pkg_arg)
+ try:
+ self.yumbase.downgrade(**build_yname(pkg_arg, inst))
+ except yum.Errors.YumBaseError:
+ yume = sys.exc_info()[1]
+ self.logger.error("Error downgrading package %s: %s" %
+ (pkg_arg, yume))
+
if len(reinstall_pkgs) > 0:
self.logger.info("Attempting to reinstall packages")
for inst in reinstall_pkgs:
diff --git a/src/lib/Bcfg2/Client/XML.py b/src/lib/Bcfg2/Client/XML.py
index 91d4ac5c6..4ba06abae 100644
--- a/src/lib/Bcfg2/Client/XML.py
+++ b/src/lib/Bcfg2/Client/XML.py
@@ -5,9 +5,29 @@
# pylint: disable=E0611,W0611,W0613,C0103
try:
- from lxml.etree import Element, SubElement, XML, tostring
+ from lxml.etree import Element, SubElement, tostring, XMLParser
from lxml.etree import XMLSyntaxError as ParseError
+ from lxml.etree import XML as _XML
+ from Bcfg2.Compat import wraps
driver = 'lxml'
+
+ # libxml2 2.9.0+ doesn't parse 10M+ documents by default:
+ # https://mail.gnome.org/archives/commits-list/2012-August/msg00645.html
+ try:
+ _parser = XMLParser(huge_tree=True)
+ except TypeError:
+ _parser = XMLParser()
+
+ @wraps(_XML)
+ def XML(val, **kwargs):
+ """ unicode strings w/encoding declaration are not supported in
+ recent lxml.etree, so we try to read XML, and if it fails we try
+ encoding the string. """
+ kwargs.setdefault('parser', _parser)
+ try:
+ return _XML(val, **kwargs)
+ except ValueError:
+ return _XML(val.encode(), **kwargs)
except ImportError:
# lxml not available
from xml.parsers.expat import ExpatError as ParseError
diff --git a/src/lib/Bcfg2/Reporting/Collector.py b/src/lib/Bcfg2/Reporting/Collector.py
index 6c1dfdccb..12c9cdaa8 100644
--- a/src/lib/Bcfg2/Reporting/Collector.py
+++ b/src/lib/Bcfg2/Reporting/Collector.py
@@ -6,6 +6,7 @@ import time
import threading
# pylint: disable=E0611
+from lockfile import LockFailed, LockTimeout
try:
from lockfile.pidlockfile import PIDLockFile
from lockfile import Error as PIDFileError
@@ -63,6 +64,8 @@ class ReportingCollector(object):
bcfg2-admin"""
self.terminate = None
self.context = None
+ self.children = []
+ self.cleanup_threshold = 25
if Bcfg2.Options.setup.debug:
level = logging.DEBUG
@@ -106,12 +109,24 @@ class ReportingCollector(object):
self.terminate = threading.Event()
atexit.register(self.shutdown)
self.context = daemon.DaemonContext(detach_process=True)
+ iter = 0
if Bcfg2.Options.setup.daemon:
self.logger.debug("Daemonizing")
try:
self.context.pidfile = PIDLockFile(Bcfg2.Options.setup.daemon)
self.context.open()
+ except LockFailed:
+ self.logger.error("Failed to daemonize: %s" %
+ sys.exc_info()[1])
+ self.shutdown()
+ return
+ except LockTimeout:
+ self.logger.error("Failed to daemonize: "
+ "Failed to acquire lock on %s" %
+ self.setup['daemon'])
+ self.shutdown()
+ return
except PIDFileError:
self.logger.error("Error writing pid file: %s" %
sys.exc_info()[1])
@@ -128,6 +143,13 @@ class ReportingCollector(object):
continue
store_thread = ReportingStoreThread(interaction, self.storage)
store_thread.start()
+ self.children.append(store_thread)
+
+ iter += 1
+ if iter >= self.cleanup_threshold:
+ self.reap_children()
+ iter = 0
+
except (SystemExit, KeyboardInterrupt):
self.logger.info("Shutting down")
self.shutdown()
@@ -147,3 +169,16 @@ class ReportingCollector(object):
pass
if self.storage:
self.storage.shutdown()
+
+ def reap_children(self):
+ """Join any non-live threads"""
+ newlist = []
+
+ self.logger.debug("Starting reap_children")
+ for child in self.children:
+ if child.isAlive():
+ newlist.append(child)
+ else:
+ child.join()
+ self.logger.debug("Joined child thread %s" % child.getName())
+ self.children = newlist
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index 992687a85..b2d26d190 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -14,6 +14,7 @@ from django.core import management
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
@@ -368,7 +369,12 @@ class DjangoORM(StorageBase):
self._import_interaction(interaction)
except:
self.logger.error("Failed to import interaction: %s" %
- sys.exc_info()[1])
+ traceback.format_exc().splitlines()[-1])
+ finally:
+ self.logger.info("%s: Closing database connection" %
+ self.__class__.__name__)
+ db.close_connection()
+
def validate(self):
"""Validate backend storage. Should be called once when loaded"""
diff --git a/src/lib/Bcfg2/Reporting/templates/base.html b/src/lib/Bcfg2/Reporting/templates/base.html
index 7edf3a949..ef6799c2b 100644
--- a/src/lib/Bcfg2/Reporting/templates/base.html
+++ b/src/lib/Bcfg2/Reporting/templates/base.html
@@ -93,7 +93,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.3.3</span>
+ <span>Bcfg2 Version 1.3.4</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/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 4f51ebe87..9c22d17ac 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -19,7 +19,7 @@ import Bcfg2.Server.Statistics
import Bcfg2.Server.FileMonitor
from itertools import chain
from Bcfg2.Server.Cache import Cache
-from Bcfg2.Compat import xmlrpclib # pylint: disable=W0622
+from Bcfg2.Compat import xmlrpclib, wraps # pylint: disable=W0622
from Bcfg2.Server.Plugin.exceptions import * # pylint: disable=W0401,W0614
from Bcfg2.Server.Plugin.interfaces import * # pylint: disable=W0401,W0614
from Bcfg2.Server.Plugin import track_statistics
@@ -74,6 +74,24 @@ def sort_xml(node, key=None):
node[:] = sorted_children
+def close_db_connection(func):
+ """ Decorator that closes the Django database connection at the end of
+ the function. This should decorate any exposed function that
+ might open a database connection. """
+ @wraps(func)
+ def inner(self, *args, **kwargs):
+ """ 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().name)
+ db.close_connection()
+ return rv
+
+ return inner
+
+
class CoreInitError(Exception):
""" Raised when the server core cannot be initialized. """
pass
@@ -196,6 +214,12 @@ class Core(object):
self.revision = '-1'
atexit.register(self.shutdown)
+ #: if :func:`Bcfg2.Server.Core.shutdown` is called explicitly,
+ #: then :mod:`atexit` calls it *again*, so it gets called
+ #: twice. This is potentially bad, so we use
+ #: :attr:`Bcfg2.Server.Core._running` as a flag to determine
+ #: if the core needs to be shutdown, and only do it once.
+ self._running = True
#: Threading event to signal worker threads (e.g.,
#: :attr:`fam_thread`) to shutdown
@@ -403,14 +427,22 @@ class Core(object):
def shutdown(self):
""" Perform plugin and FAM shutdown tasks. """
- self.logger.info("Shutting down core...")
+ if not self._running:
+ self.logger.debug("%s: Core already shut down" % self.name)
+ return
+ self.logger.info("%s: Shutting down core..." % self.name)
if not self.terminate.isSet():
self.terminate.set()
- self.fam.shutdown()
- self.logger.info("FAM shut down")
- for plugin in list(self.plugins.values()):
- plugin.shutdown()
- self.logger.info("All plugins shut down")
+ self._running = False
+ self.fam.shutdown()
+ self.logger.info("%s: FAM shut down" % self.name)
+ 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):
@@ -601,9 +633,10 @@ class Core(object):
del entry.attrib['realname']
return ret
except:
- self.logger.error("Failed binding entry %s:%s with altsrc %s" %
- (entry.tag, entry.get('realname'),
- entry.get('name')))
+ self.logger.error(
+ "Failed binding entry %s:%s with altsrc %s: %s" %
+ (entry.tag, entry.get('realname'), entry.get('name'),
+ sys.exc_info()[1]))
entry.set('name', oldname)
self.logger.error("Falling back to %s:%s" %
(entry.tag, entry.get('name')))
@@ -1052,6 +1085,7 @@ class Core(object):
@exposed
@track_statistics()
+ @close_db_connection
def DeclareVersion(self, address, version):
""" Declare the client version.
@@ -1074,6 +1108,7 @@ class Core(object):
return True
@exposed
+ @close_db_connection
def GetProbes(self, address):
""" Fetch probes for the client.
@@ -1099,6 +1134,7 @@ class Core(object):
(client, err))
@exposed
+ @close_db_connection
def RecvProbeData(self, address, probedata):
""" Receive probe data from clients.
@@ -1146,6 +1182,7 @@ class Core(object):
return True
@exposed
+ @close_db_connection
def AssertProfile(self, address, profile):
""" Set profile for a client.
@@ -1165,6 +1202,7 @@ class Core(object):
return True
@exposed
+ @close_db_connection
def GetConfig(self, address):
""" Build config for a client by calling
:func:`BuildConfiguration`.
@@ -1184,6 +1222,7 @@ class Core(object):
self.critical_error("Metadata consistency failure for %s" % client)
@exposed
+ @close_db_connection
def RecvStats(self, address, stats):
""" Act on statistics upload with :func:`process_statistics`.
@@ -1199,6 +1238,7 @@ class Core(object):
return True
@exposed
+ @close_db_connection
def GetDecisionList(self, address, mode):
""" Get the decision list for the client with :func:`GetDecisions`.
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
index b8eb06aa1..c4b34a469 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -212,7 +212,7 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
AddMonitor.__doc__ = Pseudo.AddMonitor.__doc__
def shutdown(self):
- if self.notifier:
+ if self.started and self.notifier:
self.notifier.stop()
shutdown.__doc__ = Pseudo.shutdown.__doc__
diff --git a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
index 109ace61f..22c97a0fe 100644
--- a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
+++ b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py
@@ -123,12 +123,30 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
@classmethod
def Errors(cls):
- return {"unknown-entry-type": "error",
+ return {"missing-elements": "error",
+ "unknown-entry-type": "error",
"unknown-entry-tag": "error",
"required-attrs-missing": "error",
"required-attr-format": "error",
"extra-attrs": "warning"}
+ def check_default_acl(self, path):
+ """ Check that a default ACL contains either no entries or minimum
+ required entries """
+ defaults = 0
+ if path.xpath("ACL[@type='default' and @scope='user' and @user='']"):
+ defaults += 1
+ if path.xpath("ACL[@type='default' and @scope='group' and @group='']"):
+ defaults += 1
+ if path.xpath("ACL[@type='default' and @scope='other']"):
+ defaults += 1
+ if defaults > 0 and defaults < 3:
+ self.LintError(
+ "missing-elements",
+ "A Path must have either no default ACLs or at"
+ " least default:user::, default:group:: and"
+ " default:other::")
+
def check_packages(self):
""" Check Packages sources for Source entries with missing
attributes. """
@@ -172,7 +190,7 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
rules.name))
def check_bundles(self):
- """ Check bundles for BoundPath entries with missing
+ """ Check bundles for BoundPath and BoundPackage entries with missing
attrs. """
if 'Bundler' not in self.core.plugins:
return
@@ -192,6 +210,15 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
"required-attrs-missing",
"Path tags require either a 'name' or 'glob' "
"attribute: \n%s" % self.RenderXML(path))
+ # ensure that abstract Package tags have either name
+ # or group specified
+ for package in xdata.xpath("//Package"):
+ if ('name' not in package.attrib and
+ 'group' not in package.attrib):
+ self.LintError(
+ "required-attrs-missing",
+ "Package tags require either a 'name' or 'group' "
+ "attribute: \n%s" % self.RenderXML(package))
def check_entry(self, entry, filename):
""" Generic entry check.
@@ -231,6 +258,9 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
required_attrs['major'] = is_device_mode
required_attrs['minor'] = is_device_mode
+ if tag == 'Path':
+ self.check_default_acl(entry)
+
if tag == 'ACL' and 'scope' in required_attrs:
required_attrs[entry.get('scope')] = is_username
diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py
index 3ad78ade4..0b3f1e24d 100644
--- a/src/lib/Bcfg2/Server/Lint/Validate.py
+++ b/src/lib/Bcfg2/Server/Lint/Validate.py
@@ -90,6 +90,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"xml-failed-to-parse": "error",
"xml-failed-to-read": "error",
"xml-failed-to-verify": "error",
+ "xinclude-does-not-exist": "error",
"input-output-error": "error"}
def check_properties(self):
@@ -115,6 +116,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
try:
xdata = lxml.etree.parse(filename)
if self.files is None:
+ self._expand_wildcard_xincludes(xdata)
xdata.xinclude()
return xdata
except (lxml.etree.XIncludeError, SyntaxError):
@@ -132,6 +134,33 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"Failed to open file %s" % filename)
return False
+ def _expand_wildcard_xincludes(self, xdata):
+ """ a lightweight version of
+ :func:`Bcfg2.Server.Plugin.helpers.XMLFileBacked._follow_xincludes` """
+ xinclude = '%sinclude' % Bcfg2.Server.XI_NAMESPACE
+ for el in xdata.findall('//' + xinclude):
+ name = el.get("href")
+ if name.startswith("/"):
+ fpath = name
+ else:
+ fpath = os.path.join(os.path.dirname(xdata.docinfo.URL), name)
+
+ # expand globs in xinclude, a bcfg2-specific extension
+ extras = glob.glob(fpath)
+ if not extras:
+ msg = "%s: %s does not exist, skipping: %s" % \
+ (xdata.docinfo.URL, name, self.RenderXML(el))
+ if el.findall('./%sfallback' % Bcfg2.Server.XI_NAMESPACE):
+ self.logger.debug(msg)
+ else:
+ self.LintError("xinclude-does-not-exist", msg)
+
+ parent = el.getparent()
+ parent.remove(el)
+ for extra in extras:
+ if extra != xdata.docinfo.URL:
+ lxml.etree.SubElement(parent, xinclude, href=extra)
+
def validate(self, filename, schemafile, schema=None):
""" Validate a file against the given schema.
diff --git a/src/lib/Bcfg2/Server/Lint/ValidateJSON.py b/src/lib/Bcfg2/Server/Lint/ValidateJSON.py
index 04151d764..6383a3c99 100644
--- a/src/lib/Bcfg2/Server/Lint/ValidateJSON.py
+++ b/src/lib/Bcfg2/Server/Lint/ValidateJSON.py
@@ -10,7 +10,9 @@ import Bcfg2.Server.Lint
try:
import json
-except ImportError:
+ # py2.4 json library is structured differently
+ json.loads # pylint: disable=W0104
+except (ImportError, AttributeError):
import simplejson as json
diff --git a/src/lib/Bcfg2/Server/MultiprocessingCore.py b/src/lib/Bcfg2/Server/MultiprocessingCore.py
index 294963669..724b34d8d 100644
--- a/src/lib/Bcfg2/Server/MultiprocessingCore.py
+++ b/src/lib/Bcfg2/Server/MultiprocessingCore.py
@@ -275,6 +275,7 @@ class ChildCore(Core):
@exposed
def GetConfig(self, client):
""" Render the configuration for a client """
+ self.metadata.update_client_list()
self.logger.debug("%s: Building configuration for %s" %
(self.name, client))
return lxml.etree.tostring(self.BuildConfiguration(client))
diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py
index aa8db2bc0..407e9df46 100644
--- a/src/lib/Bcfg2/Server/Plugin/helpers.py
+++ b/src/lib/Bcfg2/Server/Plugin/helpers.py
@@ -275,7 +275,8 @@ class PluginDatabaseModel(object):
inherit from. This is just a mixin; models must also inherit from
django.db.models.Model to be valid Django models."""
- class Meta: # pylint: disable=C0111,W0232
+ class Meta(object): # pylint: disable=W0232
+ """ Model metadata options """
app_label = "Server"
diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py
index 622b69c79..c45d6fa84 100644
--- a/src/lib/Bcfg2/Server/Plugin/interfaces.py
+++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py
@@ -216,6 +216,10 @@ class Metadata(object):
"""
raise NotImplementedError
+ def update_client_list(self):
+ """ Re-read the cached list of clients """
+ raise NotImplementedError
+
class Connector(object):
""" Connector plugins augment client metadata instances with
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index 6ff256147..bf51ff678 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -686,7 +686,7 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
client = MetadataClientModel(hostname=client_name)
# pylint: enable=E1102
client.save()
- self.clients = self.list_clients()
+ self.update_client_list()
return client
else:
try:
@@ -739,7 +739,15 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
attribs, alias=True)
def list_clients(self):
- """ List all clients in client database """
+ """ List all clients in client database.
+
+ Making ``self.clients`` a property and reading the client list
+ dynamically from the database on every call to
+ ``self.clients`` can result in very high rates of database
+ reads, so we cache the ``list_clients()`` results to reduce
+ the database load. When the database is in use, the client
+ list is reread periodically with
+ :func:`Bcfg2.Server.Plugins.Metadata.update_client_list`. """
if self._use_db:
return set([c.hostname for c in MetadataClientModel.objects.all()])
else:
@@ -790,7 +798,7 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
self.logger.warning(msg)
raise Bcfg2.Server.Plugin.MetadataConsistencyError(msg)
client.delete()
- self.clients = self.list_clients()
+ self.update_client_list()
else:
return self._remove_xdata(self.clients_xml, "Client", client_name)
@@ -859,8 +867,7 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
except KeyError:
self.clientgroups[clname] = [profile]
self.states['clients.xml'] = True
- if self._use_db:
- self.clients = self.list_clients()
+ self.update_client_list()
def _get_condition(self, element):
""" Return a predicate that returns True if a client meets
@@ -1452,6 +1459,30 @@ class Metadata(Bcfg2.Server.Plugin.Metadata,
return True
# pylint: enable=R0911,R0912
+ def update_client_list(self):
+ """ Re-read the client list from the database (if the database is in
+ use) """
+ if self._use_db:
+ self.logger.debug("Metadata: Re-reading client list from database")
+ old = set(self.clients)
+ self.clients = self.list_clients()
+ new = set(self.clients)
+ added = new - old
+ removed = old - new
+ self.logger.debug("Metadata: Added %s clients: %s" %
+ (len(added), added))
+ self.logger.debug("Metadata: Removed %s clients: %s" %
+ (len(removed), removed))
+ # we could do this with set.symmetric_difference(), but we
+ # want detailed numbers of added/removed clients for
+ # logging
+ for client in added.union(removed):
+ self.expire_cache(client)
+
+ def start_client_run(self, metadata):
+ """ Hook to reread client list if the database is in use """
+ self.update_client_list()
+
def end_statistics(self, metadata):
""" Hook to toggle clients in bootstrap mode """
if self.auth.get(metadata.hostname,
diff --git a/src/lib/Bcfg2/Server/Plugins/Ohai.py b/src/lib/Bcfg2/Server/Plugins/Ohai.py
index ba7baab11..c5fb46c97 100644
--- a/src/lib/Bcfg2/Server/Plugins/Ohai.py
+++ b/src/lib/Bcfg2/Server/Plugins/Ohai.py
@@ -10,7 +10,9 @@ import Bcfg2.Server.Plugin
try:
import json
-except ImportError:
+ # py2.4 json library is structured differently
+ json.loads # pylint: disable=W0104
+except (ImportError, AttributeError):
import simplejson as json
PROBECODE = """#!/bin/sh
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
index dba56eed2..c1d53a78e 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py
@@ -72,6 +72,7 @@ class AptSource(Source):
def read_files(self):
bdeps = dict()
bprov = dict()
+ self.essentialpkgs = set()
depfnames = ['Depends', 'Pre-Depends']
if self.recommended:
depfnames.append('Recommends')
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index 6a493c19d..3cfda9e9c 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -90,7 +90,9 @@ try:
import yum
try:
import json
- except ImportError:
+ # py2.4 json library is structured differently
+ json.loads # pylint: disable=W0104
+ except (ImportError, AttributeError):
import simplejson as json
HAS_YUM = True
except ImportError:
@@ -354,8 +356,8 @@ class YumCollection(Collection):
self.__class__._helper = find_executable('bcfg2-yum-helper')
if not self.__class__._helper:
self.__class__._helper = "/usr/sbin/bcfg2-yum-helper"
- # pylint: enable=W0212
- return self._helper
+ return self.__class__._helper
+ # pylint: enable=W0212
@property
def use_yum(self):
diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py
index 553c16202..21d50ace6 100644
--- a/src/lib/Bcfg2/Server/Plugins/Probes.py
+++ b/src/lib/Bcfg2/Server/Plugins/Probes.py
@@ -51,8 +51,10 @@ def load_django_models():
try:
import json
+ # py2.4 json library is structured differently
+ json.loads # pylint: disable=W0104
HAS_JSON = True
-except ImportError:
+except (ImportError, AttributeError):
try:
import simplejson as json
HAS_JSON = True
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index 87cee7029..04314218c 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -13,8 +13,10 @@ from Bcfg2.Server.Plugin import PluginExecutionError
try:
import json
+ # py2.4 json library is structured differently
+ json.loads # pylint: disable=W0104
HAS_JSON = True
-except ImportError:
+except (ImportError, AttributeError):
try:
import simplejson as json
HAS_JSON = True
diff --git a/src/lib/Bcfg2/version.py b/src/lib/Bcfg2/version.py
index 35d4cfa0a..ae82724f3 100644
--- a/src/lib/Bcfg2/version.py
+++ b/src/lib/Bcfg2/version.py
@@ -2,7 +2,7 @@
import re
-__version__ = "1.3.3"
+__version__ = "1.3.4"
class Bcfg2VersionInfo(tuple): # pylint: disable=E0012,R0924
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py
index 74ca617af..b8534f5a8 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py
@@ -48,203 +48,200 @@ test_data = """<Test>
test_xdata = lxml.etree.XML(test_data)
-class TestPOSIXAugeas(TestPOSIXTool):
- test_obj = POSIXAugeas
-
- applied_commands = dict(
- insert=lxml.etree.Element(
- "Insert", label="Thing",
- path='Test/Children[#attribute/identical = "true"]/Thing'),
- set=lxml.etree.Element("Set", path="Test/Text/#text",
- value="content with spaces"),
- move=lxml.etree.Element(
- "Move", source="Test/Foo",
- destination='Test/Children[#attribute/identical = "false"]/Foo'),
- remove=lxml.etree.Element("Remove", path="Test/Bar"),
- clear=lxml.etree.Element("Clear", path="Test/Empty/#text"),
- setm=lxml.etree.Element(
- "SetMulti", sub="#text", value="same",
- base='Test/Children[#attribute/multi = "true"]/Thing'))
-
- @skipUnless(HAS_AUGEAS, "Python Augeas libraries not found")
- def setUp(self):
- TestPOSIXTool.setUp(self)
- fd, self.tmpfile = tempfile.mkstemp()
- os.fdopen(fd, 'w').write(test_data)
-
- def tearDown(self):
- tmpfile = getattr(self, "tmpfile", None)
- if tmpfile:
- os.unlink(tmpfile)
-
- def test_fully_specified(self):
- ptool = self.get_obj()
-
- entry = lxml.etree.Element("Path", name="/test", type="augeas")
- self.assertFalse(ptool.fully_specified(entry))
-
- entry.text = "text"
- self.assertTrue(ptool.fully_specified(entry))
-
- def test_install(self):
- # this is tested adequately by the other tests
- pass
-
- def test_verify(self):
- # this is tested adequately by the other tests
- pass
-
- @patch("Bcfg2.Client.Tools.POSIX.Augeas.POSIXTool.verify")
- def _verify(self, commands, mock_verify):
- ptool = self.get_obj()
- mock_verify.return_value = True
-
- entry = lxml.etree.Element("Path", name=self.tmpfile,
- type="augeas", lens="Xml")
- entry.extend(commands)
-
- modlist = []
- self.assertTrue(ptool.verify(entry, modlist))
- mock_verify.assert_called_with(ptool, entry, modlist)
- self.assertXMLEqual(lxml.etree.parse(self.tmpfile).getroot(),
- test_xdata)
-
- def test_verify_insert(self):
- """ Test successfully verifying an Insert command """
- self._verify([self.applied_commands['insert']])
-
- def test_verify_set(self):
- """ Test successfully verifying a Set command """
- self._verify([self.applied_commands['set']])
-
- def test_verify_move(self):
- """ Test successfully verifying a Move command """
- self._verify([self.applied_commands['move']])
-
- def test_verify_remove(self):
- """ Test successfully verifying a Remove command """
- self._verify([self.applied_commands['remove']])
-
- def test_verify_clear(self):
- """ Test successfully verifying a Clear command """
- self._verify([self.applied_commands['clear']])
-
- def test_verify_set_multi(self):
- """ Test successfully verifying a SetMulti command """
- self._verify([self.applied_commands['setm']])
-
- def test_verify_all(self):
- """ Test successfully verifying multiple commands """
- self._verify(self.applied_commands.values())
-
- @patch("Bcfg2.Client.Tools.POSIX.Augeas.POSIXTool.install")
- def _install(self, commands, expected, mock_install):
- ptool = self.get_obj()
- mock_install.return_value = True
-
- entry = lxml.etree.Element("Path", name=self.tmpfile,
- type="augeas", lens="Xml")
- entry.extend(commands)
-
- self.assertTrue(ptool.install(entry))
- mock_install.assert_called_with(ptool, entry)
- self.assertXMLEqual(lxml.etree.parse(self.tmpfile).getroot(),
- expected)
-
- def test_install_set_existing(self):
- """ Test setting the value of an existing node """
- expected = copy.deepcopy(test_xdata)
- expected.find("Text").text = "Changed content"
- self._install([lxml.etree.Element("Set", path="Test/Text/#text",
- value="Changed content",
- verified="false")],
- expected)
-
- def test_install_set_new(self):
- """ Test setting the value of an new node """
- expected = copy.deepcopy(test_xdata)
- newtext = lxml.etree.SubElement(expected, "NewText")
- newtext.text = "new content"
- self._install([lxml.etree.Element("Set", path="Test/NewText/#text",
- value="new content",
- verified="false")],
- expected)
-
- def test_install_only_verified(self):
- """ Test that only unverified commands are installed """
- expected = copy.deepcopy(test_xdata)
- newtext = lxml.etree.SubElement(expected, "NewText")
- newtext.text = "new content"
- self._install(
- [lxml.etree.Element("Set", path="Test/NewText/#text",
- value="new content", verified="false"),
- lxml.etree.Element("Set", path="Test/Bogus/#text",
- value="bogus", verified="true")],
- expected)
-
- def test_install_remove(self):
- """ Test removing a node """
- expected = copy.deepcopy(test_xdata)
- expected.remove(expected.find("Attrs"))
- self._install(
- [lxml.etree.Element("Remove",
- path='Test/*[#attribute/foo = "foo"]',
- verified="false")],
- expected)
-
- def test_install_move(self):
- """ Test moving a node """
- expected = copy.deepcopy(test_xdata)
- foo = expected.xpath("//Foo")[0]
- expected.append(foo)
- self._install(
- [lxml.etree.Element("Move", source='Test/Children/Foo',
- destination='Test/Foo',
- verified="false")],
- expected)
-
- def test_install_clear(self):
- """ Test clearing a node """
- # TODO: clearing a node doesn't seem to work with the XML lens
- #
- # % augtool -b
- # augtool> set /augeas/load/Xml/incl[3] "/tmp/test.xml"
- # augtool> load
- # augtool> clear '/files/tmp/test.xml/Test/Text/#text'
- # augtool> save
- # error: Failed to execute command
- # saving failed (run 'print /augeas//error' for details)
- # augtool> print /augeas//error
- #
- # The error isn't useful.
- pass
-
- def test_install_set_multi(self):
- """ Test setting multiple nodes at once """
- expected = copy.deepcopy(test_xdata)
- for thing in expected.xpath("Children[@identical='true']/Thing"):
- thing.text = "same"
- self._install(
- [lxml.etree.Element(
- "SetMulti", value="same",
- base='Test/Children[#attribute/identical = "true"]',
- sub="Thing/#text", verified="false")],
- expected)
-
- def test_install_insert(self):
- """ Test inserting a node """
- expected = copy.deepcopy(test_xdata)
- children = expected.xpath("Children[@identical='true']")[0]
- thing = lxml.etree.Element("Thing")
- thing.text = "three"
- children.append(thing)
- self._install(
- [lxml.etree.Element(
- "Insert",
- path='Test/Children[#attribute/identical = "true"]/Thing[2]',
- label="Thing", where="after", verified="false"),
- lxml.etree.Element(
- "Set",
- path='Test/Children[#attribute/identical = "true"]/Thing[3]/#text',
- value="three", verified="false")],
- expected)
+if can_skip or HAS_AUGEAS:
+ class TestPOSIXAugeas(TestPOSIXTool):
+ test_obj = POSIXAugeas
+
+ applied_commands = dict(
+ insert=lxml.etree.Element(
+ "Insert", label="Thing",
+ path='Test/Children[#attribute/identical = "true"]/Thing'),
+ set=lxml.etree.Element("Set", path="Test/Text/#text",
+ value="content with spaces"),
+ move=lxml.etree.Element(
+ "Move", source="Test/Foo",
+ destination='Test/Children[#attribute/identical = "false"]/Foo'),
+ remove=lxml.etree.Element("Remove", path="Test/Bar"),
+ clear=lxml.etree.Element("Clear", path="Test/Empty/#text"),
+ setm=lxml.etree.Element(
+ "SetMulti", sub="#text", value="same",
+ base='Test/Children[#attribute/multi = "true"]/Thing'))
+
+ @skipUnless(HAS_AUGEAS, "Python Augeas libraries not found")
+ def setUp(self):
+ fd, self.tmpfile = tempfile.mkstemp()
+ os.fdopen(fd, 'w').write(test_data)
+
+ def tearDown(self):
+ tmpfile = getattr(self, "tmpfile", None)
+ if tmpfile and os.path.exists(tmpfile):
+ os.unlink(tmpfile)
+
+ def test_fully_specified(self):
+ ptool = self.get_obj()
+
+ entry = lxml.etree.Element("Path", name="/test", type="augeas")
+ self.assertFalse(ptool.fully_specified(entry))
+
+ lxml.etree.SubElement(entry, "Set", path="/test", value="test")
+ self.assertTrue(ptool.fully_specified(entry))
+
+ def test_install(self):
+ # this is tested adequately by the other tests
+ pass
+
+ def test_verify(self):
+ # this is tested adequately by the other tests
+ pass
+
+ @patch("Bcfg2.Client.Tools.POSIX.Augeas.POSIXTool.verify")
+ def _verify(self, commands, mock_verify):
+ ptool = self.get_obj()
+ mock_verify.return_value = True
+
+ entry = lxml.etree.Element("Path", name=self.tmpfile,
+ type="augeas", lens="Xml")
+ entry.extend(commands)
+
+ modlist = []
+ self.assertTrue(ptool.verify(entry, modlist))
+ mock_verify.assert_called_with(ptool, entry, modlist)
+ self.assertXMLEqual(lxml.etree.parse(self.tmpfile).getroot(),
+ test_xdata)
+
+ def test_verify_insert(self):
+ """ Test successfully verifying an Insert command """
+ self._verify([self.applied_commands['insert']])
+
+ def test_verify_set(self):
+ """ Test successfully verifying a Set command """
+ self._verify([self.applied_commands['set']])
+
+ def test_verify_move(self):
+ """ Test successfully verifying a Move command """
+ self._verify([self.applied_commands['move']])
+
+ def test_verify_remove(self):
+ """ Test successfully verifying a Remove command """
+ self._verify([self.applied_commands['remove']])
+
+ def test_verify_clear(self):
+ """ Test successfully verifying a Clear command """
+ self._verify([self.applied_commands['clear']])
+
+ def test_verify_set_multi(self):
+ """ Test successfully verifying a SetMulti command """
+ self._verify([self.applied_commands['setm']])
+
+ def test_verify_all(self):
+ """ Test successfully verifying multiple commands """
+ self._verify(self.applied_commands.values())
+
+ @patch("Bcfg2.Client.Tools.POSIX.Augeas.POSIXTool.install")
+ def _install(self, commands, expected, mock_install, **attrs):
+ ptool = self.get_obj()
+ mock_install.return_value = True
+
+ entry = lxml.etree.Element("Path", name=self.tmpfile,
+ type="augeas", lens="Xml")
+ for key, val in attrs.items():
+ entry.set(key, val)
+ entry.extend(commands)
+
+ self.assertTrue(ptool.install(entry))
+ mock_install.assert_called_with(ptool, entry)
+ self.assertXMLEqual(lxml.etree.parse(self.tmpfile).getroot(),
+ expected)
+
+ def test_install_set_existing(self):
+ """ Test setting the value of an existing node """
+ expected = copy.deepcopy(test_xdata)
+ expected.find("Text").text = "Changed content"
+ self._install([lxml.etree.Element("Set", path="Test/Text/#text",
+ value="Changed content")],
+ expected)
+
+ def test_install_set_new(self):
+ """ Test setting the value of an new node """
+ expected = copy.deepcopy(test_xdata)
+ newtext = lxml.etree.SubElement(expected, "NewText")
+ newtext.text = "new content"
+ self._install([lxml.etree.Element("Set", path="Test/NewText/#text",
+ value="new content")],
+ expected)
+
+ def test_install_remove(self):
+ """ Test removing a node """
+ expected = copy.deepcopy(test_xdata)
+ expected.remove(expected.find("Attrs"))
+ self._install(
+ [lxml.etree.Element("Remove",
+ path='Test/*[#attribute/foo = "foo"]')],
+ expected)
+
+ def test_install_move(self):
+ """ Test moving a node """
+ expected = copy.deepcopy(test_xdata)
+ foo = expected.xpath("//Foo")[0]
+ expected.append(foo)
+ self._install(
+ [lxml.etree.Element("Move", source='Test/Children/Foo',
+ destination='Test/Foo')],
+ expected)
+
+ def test_install_clear(self):
+ """ Test clearing a node """
+ # TODO: clearing a node doesn't seem to work with the XML lens
+ #
+ # % augtool -b
+ # augtool> set /augeas/load/Xml/incl[3] "/tmp/test.xml"
+ # augtool> load
+ # augtool> clear '/files/tmp/test.xml/Test/Text/#text'
+ # augtool> save
+ # error: Failed to execute command
+ # saving failed (run 'print /augeas//error' for details)
+ # augtool> print /augeas//error
+ #
+ # The error isn't useful.
+ pass
+
+ def test_install_set_multi(self):
+ """ Test setting multiple nodes at once """
+ expected = copy.deepcopy(test_xdata)
+ for thing in expected.xpath("Children[@identical='true']/Thing"):
+ thing.text = "same"
+ self._install(
+ [lxml.etree.Element(
+ "SetMulti", value="same",
+ base='Test/Children[#attribute/identical = "true"]',
+ sub="Thing/#text")],
+ expected)
+
+ def test_install_insert(self):
+ """ Test inserting a node """
+ expected = copy.deepcopy(test_xdata)
+ children = expected.xpath("Children[@identical='true']")[0]
+ thing = lxml.etree.Element("Thing")
+ thing.text = "three"
+ children.append(thing)
+ self._install(
+ [lxml.etree.Element(
+ "Insert",
+ path='Test/Children[#attribute/identical = "true"]/Thing[2]',
+ label="Thing", where="after"),
+ lxml.etree.Element(
+ "Set",
+ path='Test/Children[#attribute/identical = "true"]/Thing[3]/#text',
+ value="three")],
+ expected)
+
+ def test_install_initial(self):
+ """ Test creating initial content and then modifying it """
+ os.unlink(self.tmpfile)
+ expected = copy.deepcopy(test_xdata)
+ expected.find("Text").text = "Changed content"
+ initial = lxml.etree.Element("Initial")
+ initial.text = test_data
+ modify = lxml.etree.Element("Set", path="Test/Text/#text",
+ value="Changed content")
+ self._install([initial, modify], expected, current_exists="false")
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
index 5a752b2ac..ea4ca3f5f 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
@@ -586,16 +586,16 @@ class TestPOSIXTool(TestTool):
self.assertEqual(6, ptool._norm_acl_perms("rwa"))
self.assertEqual(4, ptool._norm_acl_perms("rr"))
- @patch('os.stat')
- def test__gather_data(self, mock_stat):
+ @patch('os.lstat')
+ def test__gather_data(self, mock_lstat):
ptool = self.get_obj()
path = '/test'
- mock_stat.side_effect = OSError
+ mock_lstat.side_effect = OSError
self.assertFalse(ptool._gather_data(path)[0])
- mock_stat.assert_called_with(path)
+ mock_lstat.assert_called_with(path)
- mock_stat.reset_mock()
- mock_stat.side_effect = None
+ mock_lstat.reset_mock()
+ mock_lstat.side_effect = None
# create a return value
stat_rv = MagicMock()
def stat_getitem(key):
@@ -608,7 +608,7 @@ class TestPOSIXTool(TestTool):
# and ensure that they're stripped
return int('060660', 8)
stat_rv.__getitem__ = Mock(side_effect=stat_getitem)
- mock_stat.return_value = stat_rv
+ mock_lstat.return_value = stat_rv
# disable selinux and acls for this call -- we test them
# separately so that we can skip those tests as appropriate
@@ -620,7 +620,7 @@ class TestPOSIXTool(TestTool):
(stat_rv, '0', '10', '0660', None, None))
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX, \
Bcfg2.Client.Tools.POSIX.base.HAS_ACLS = states
- mock_stat.assert_called_with(path)
+ mock_lstat.assert_called_with(path)
@skipUnless(HAS_SELINUX, "SELinux not found, skipping")
def test__gather_data_selinux(self):
@@ -628,12 +628,12 @@ class TestPOSIXTool(TestTool):
context = 'system_u:object_r:root_t:s0'
path = '/test'
- @patch('os.stat')
- @patchIf(HAS_SELINUX, "selinux.getfilecon")
- def inner(mock_getfilecon, mock_stat):
- mock_getfilecon.return_value = [len(context) + 1, context]
- mock_stat.return_value = MagicMock()
- mock_stat.return_value.__getitem__.return_value = MagicMock()
+ @patch('os.lstat')
+ @patchIf(HAS_SELINUX, "selinux.lgetfilecon")
+ def inner(mock_lgetfilecon, mock_lstat):
+ mock_lgetfilecon.return_value = [len(context) + 1, context]
+ mock_lstat.return_value = MagicMock()
+ mock_lstat.return_value.__getitem__.return_value = MagicMock()
# disable acls for this call and test them separately
state = (Bcfg2.Client.Tools.POSIX.base.HAS_ACLS,
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX)
@@ -642,30 +642,40 @@ class TestPOSIXTool(TestTool):
self.assertEqual(ptool._gather_data(path)[4], 'root_t')
Bcfg2.Client.Tools.POSIX.base.HAS_ACLS, \
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX = state
- mock_getfilecon.assert_called_with(path)
+ mock_lgetfilecon.assert_called_with(path)
inner()
@skipUnless(HAS_ACLS, "ACLS not found, skipping")
- @patch('os.stat')
- def test__gather_data_acls(self, mock_stat):
+ @patch('os.lstat')
+ @patch('stat.S_ISLNK')
+ def test__gather_data_acls(self, mock_S_ISLNK, mock_lstat):
ptool = self.get_obj()
ptool._list_file_acls = Mock()
acls = {("default", posix1e.ACL_USER, "testuser"): "rwx",
("access", posix1e.ACL_GROUP, "testgroup"): "rx"}
ptool._list_file_acls.return_value = acls
path = '/test'
- mock_stat.return_value = MagicMock()
- mock_stat.return_value.__getitem__.return_value = MagicMock()
+ mock_lstat.return_value = MagicMock()
+ mock_lstat.return_value.__getitem__.return_value = MagicMock()
+ mock_S_ISLNK.return_value = False
# disable selinux for this call and test it separately
state = (Bcfg2.Client.Tools.POSIX.base.HAS_ACLS,
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX)
Bcfg2.Client.Tools.POSIX.base.HAS_ACLS = True
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX = False
self.assertItemsEqual(ptool._gather_data(path)[5], acls)
+ ptool._list_file_acls.assert_called_with(path)
+
+ # symlinks can't have their own ACLs, so ensure that the
+ # _list_file_acls call is skipped and no ACLs are returned
+ mock_S_ISLNK.return_value = True
+ ptool._list_file_acls.reset_mock()
+ self.assertEqual(ptool._gather_data(path)[5], None)
+ self.assertFalse(ptool._list_file_acls.called)
+
Bcfg2.Client.Tools.POSIX.base.HAS_ACLS, \
Bcfg2.Client.Tools.POSIX.base.HAS_SELINUX = state
- ptool._list_file_acls.assert_called_with(path)
@patchIf(HAS_SELINUX, "selinux.matchpathcon")
def test_verify_metadata(self, mock_matchpathcon):
diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf
index e13a51d0d..1d3ba8c88 100644
--- a/testsuite/pylintrc.conf
+++ b/testsuite/pylintrc.conf
@@ -99,6 +99,10 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
# evaluation report (RP0004).
comment=no
+# Template used to display messages. This is a python new-style format string
+# used to format the massage information. See doc for all details
+msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
+
[VARIABLES]
@@ -131,6 +135,9 @@ ignore-docstrings=yes
# Maximum number of characters on a single line.
max-line-length=79
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?(<?https?://\S+>?|:(func|class):.*)$
+
# Maximum number of lines in a module
max-module-lines=1000
@@ -247,8 +254,10 @@ max-locals=20
# Maximum number of return / yield for function / method body
max-returns=6
-# Maximum number of branch for function / method body
+# Maximum number of branch for function / method body (max-branchs is
+# pylint 0.x, max-branches is 1.0)
max-branchs=18
+max-branches=18
# Maximum number of statements in function / method body
max-statements=75
diff --git a/tools/export.py b/tools/export.py
index bdb85de41..6b0238bbb 100755
--- a/tools/export.py
+++ b/tools/export.py
@@ -163,43 +163,6 @@ E.G. 1.2.0pre1 is a valid version.
print(help_message)
quit()
- if version_info['build'] == '':
- rpmchangelog = ["* %s %s <%s> %s-1\n" %
- (datetime.datetime.now().strftime("%a %b %d %Y"),
- name, email, version_release),
- "- New upstream release\n", "\n"]
- else:
- rpmchangelog = ["* %s %s <%s> %s-0.%s.%s\n" %
- (datetime.datetime.now().strftime("%a %b %d %Y"),
- name, email, version_release,
- version_info['build'][-1], version_info['build']),
- "- New upstream release\n", "\n"]
-
- # write out the new RPM changelog
- specs = ["misc/bcfg2.spec",
- "misc/bcfg2-selinux.spec"]
- if options.dryrun:
- print("*** Add the following to the top of the %%changelog section in %s:\n%s\n"
- % (rpmchangelog, " and ".join(specs)))
- else:
- for fname in specs:
- try:
- lines = open(fname).readlines()
- for lineno in range(len(lines)):
- if lines[lineno].startswith("%changelog"):
- break
- else:
- print("No %changelog section found in %s" % fname)
- continue
- for line in reversed(rpmchangelog):
- lines.insert(lineno + 1, line)
- open(fname, 'w').write("".join(lines))
- except:
- err = sys.exc_info()[1]
- print("Could not write %s: %s" % (fname, err))
- print(help_message)
- quit()
-
# update solaris version
find_and_replace('solaris/Makefile', 'VERS=',
'VERS=%s-1\n' % version,
@@ -307,12 +270,12 @@ E.G. 1.2.0pre1 is a valid version.
# http://trac.mcs.anl.gov/projects/bcfg2/ticket/1129
find_and_replace('misc/bcfg2.spec',
'Source0',
- 'Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%%{name}-%%{version}%s.tar.gz\n' % version_info["build"],
+ 'Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%{name}-%{version}%{?_pre_rc}.tar.gz\n',
startswith=True,
dryrun=options.dryrun)
find_and_replace('misc/bcfg2-selinux.spec',
'Source0',
- 'Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%%{name}-%%{version}%s.tar.gz\n' % version_info["build"],
+ 'Source0: ftp://ftp.mcs.anl.gov/pub/bcfg/%{name}-%{version}%{?_pre_rc}.tar.gz\n',
startswith=True,
dryrun=options.dryrun)
# update the version in reports