diff options
65 files changed, 1368 insertions, 520 deletions
@@ -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><Test/></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 |