diff options
author | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-09-26 09:48:07 -0400 |
---|---|---|
committer | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-09-26 10:05:22 -0400 |
commit | 34e5287c4b18ba5bfdbb74b729ceebb2a1f1637e (patch) | |
tree | bb36dd2684745ca5723c7c4909c912971f050d25 | |
parent | cd47ea485e6be6327c3b67323dc55c0533d4f256 (diff) | |
download | bcfg2-34e5287c4b18ba5bfdbb74b729ceebb2a1f1637e.tar.gz bcfg2-34e5287c4b18ba5bfdbb74b729ceebb2a1f1637e.tar.bz2 bcfg2-34e5287c4b18ba5bfdbb74b729ceebb2a1f1637e.zip |
deprecated YUM24 tool, renamed YUMng to YUM, RPMng to RPM
-rw-r--r-- | doc/client/tools.txt | 33 | ||||
-rw-r--r-- | doc/client/tools/yum.txt | 346 | ||||
-rw-r--r-- | doc/client/tools/yumng.txt | 803 | ||||
-rw-r--r-- | doc/server/plugins/generators/pkgmgr.txt | 309 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Frame.py | 15 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/RPM.py | 988 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/RPMng.py | 989 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/YUM.py | 951 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/YUM24.py | 35 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/YUMng.py | 955 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/__init__.py | 3 | ||||
-rw-r--r-- | src/lib/Bcfg2/Options.py | 130 |
12 files changed, 2641 insertions, 2916 deletions
diff --git a/doc/client/tools.txt b/doc/client/tools.txt index bff2e97ed..1dbb33b1a 100644 --- a/doc/client/tools.txt +++ b/doc/client/tools.txt @@ -130,16 +130,10 @@ as Gentoo. RPM --- -.. warning:: Deprecated in favor of :ref:`RPMng <client-tools-yumng>` +Executes RPM to manage packages on Redhat-based and similar systems. +Consider using the :ref:`YUM <client-tools-yum>` tool instead if possible. -Executes rpm to manage packages most often on redhat based systems. - -RPMng ------ - -Next-generation RPM tool. Handles RPM sublties like epoch and -prelinking and 64-bit platforms better than the RPM client -tool. :ref:`client-tools-yumng` +Formerly called ``RPMng``, but was renamed for the 1.3 release. SMF --- @@ -157,12 +151,6 @@ Systemd Systemd service support. -Example: - -.. code-block:: xml - - <Service name='udev' status='on' type='systemd'/> - SYSV ---- @@ -175,15 +163,16 @@ Upstart service support. Uses `Upstart`_ to configure services. .. _Upstart: http://upstart.ubuntu.com/ -Yum +YUM --- -.. warning:: Deprecated in favor of :ref:`YUMng <client-tools-yumng>` - -Handles RPMs using the YUM package manager. +Handles RPMs using the YUM package manager. Renamed from ``YUMng`` for +the 1.3 release. See :ref:`client-tools-yum` for more details. -YUMng +YUM24 ----- -Handles RPMs using the YUM package manager. Handles sublties better than -the Yum client tool. :ref:`client-tools-yumng` +.. warning:: Deprecated in favor of :ref:`YUM <client-tools-yum>` + +Handles RPMs using older versions of the YUM package manager. + diff --git a/doc/client/tools/yum.txt b/doc/client/tools/yum.txt new file mode 100644 index 000000000..10c3cf725 --- /dev/null +++ b/doc/client/tools/yum.txt @@ -0,0 +1,346 @@ +.. -*- mode: rst -*- + +.. _client-tools-yum: + +============================ +Bcfg2 RPM/YUM Client Drivers +============================ + +The RPM and YUM client drivers provide client support for RPMs +(installed directly from URLs) and Yum repositories. These drivers +were formerly called ``RPMng`` and ``YUMng``, respectively, but were +renamed for Bcfg2 1.3.0. + +Features +======== + +* Full RPM package identification using epoch, version, release and + arch. +* Support for multiple instances of packages with the Instance tag. +* Better control of the RPM verification using the pkg_checks, + pkg_verify and verify_flags attributes. +* Support for install only packages such as the kernel packages. +* Support for per instance ignoring of individual files for the RPM + verification with the Ignore tag. +* Multiple package Instances with full version information listed in + interactive mode. +* Support for installation and removal of gpg-pubkey packages. +* Support for controlling what action is taken on package verification + failure with the install_action, version_fail_action and + verify_fail_action attributes. + +Installation +============ + +isprelink +--------- + +``isprelink`` is a Python module that can greatly improve the +performance of the ``RPM`` driver. It should be installed on any +system that has prelink installed and will be using the ``RPM`` driver. + +Source can be found at ftp://ftp.mcs.anl.gov/pub/bcfg/isprelink-0.1.2.tar.gz + +To compile and install prelink, execute:: + + python setup.py install + +in the rpmtools directory. The elfutils-libelf-devel package is required +for the compilation. + +There may also be RPMs available in the repositories for your distro. + +Configuration and Usage +======================= + +Loading of RPM +-------------- + +The RPM driver can be loaded by command line options, client +configuration file options or as the default driver for RPM packages. + +From the command line:: + + bcfg2 -n -v -d -D Action,POSIX,Chkconfig,RPM + +This produces quite a bit of output so you may want to redirect the +output to a file for review. + +In the ``bcfg2.conf`` file:: + + [client] + drivers = Action,Chkconfig,POSIX,RPM + +Configuration File Options +-------------------------- + +A number of paramters can be set in the client configuration for both +the RPM and YUM drivers. Each driver has its own section (``[RPM]`` or +``[YUM]``), and most of the same options are accepted by each driver. +An example config might look like this:: + + [RPM] + pkg_checks = true + pkg_verify = true + erase_flags = allmatches + installonlypackages = kernel, kernel-bigmem, kernel-enterprise, kernel-smp, kernel-modules, kernel-debug, kernel-unsupported, kernel-source, kernel-devel, kernel-default, kernel-largesmp-devel, kernel-largesmp, kernel-xen, gpg-pubkey + install_action = install + version_fail_action = upgrade + verify_fail_action = reinstall + +installonlypackages +^^^^^^^^^^^^^^^^^^^ + +Install-only packages are packages that should only ever be installed +or deleted, not upgraded. + +It is best practice to only ever install/delete kernel packages, the +wisdom being that the package for the currently running kernel should +always be installed. Doing an upgrade would delete the running kernel +package. + +``gpg-pubkey`` will be automatically added to the list of install-only +packages. + +Example:: + + [RPM] + installonlypackages = kernel, kernel-bigmem, kernel-enterprise, kernel-smp, kernel-modules, kernel-debug, kernel-unsupported, kernel-source, kernel-devel, kernel-default, kernel-largesmp-devel, kernel-largesmp, kernel-xen, gpg-pubkey + +This option is not honored by the ``YUM`` driver. + +erase_flags +^^^^^^^^^^^ + +erase_flags are rpm options used by 'rpm -erase' in the client ``Remove()`` +method. The RPM erase is written using rpm-python and does not use +the rpm command. + +The erase flags are specified in the client configuration file as a +comma separated list and apply to all RPM erase operations. The +following rpm erase options are supported. See the rpm man page for +details:: + + noscripts + notriggers + repackage + allmatches + nodeps + +This option is not honored by the ``YUM`` driver. + +pkg_checks +^^^^^^^^^^ + +The RPM/YUM drivers do the following three checks/status: + +#. Installed +#. Version +#. rpm verify + +Setting pkg_checks = true (the default) in the client configuration file +means that all three checks will be done for all packages. + +Setting pkg_checks = false in the client configuration file means that +only the Installed check will be done for all packages. + +The true/false value can be any combination of upper and lower case. + +.. note:: + #. pkg_checks must evaluate true for both the client (this option) + and the package (see the Package Tag pkg_checks attribute + below) for the action to take place. + #. If pkg_checks = false then the Pkgmgr entries do not need the + version information. See the examples towards the bottom of + the page. + +pkg_verify +^^^^^^^^^^ + +The RPM/YUM drivers do the following three checks/status: + +#. Installed +#. Version +#. rpm verify + +Setting pkg_verify = true (the default) in the client configuration +file means that all three checks will be done for all packages as long +as pkg_checks = true. + +Setting pkg_verify = false in the client configuration file means that +the rpm verify wil not be done for all packages on the client. + +The true/false value can be any combination of upper and lower case. + +.. note:: + #. pkg_verify must evaluate true for both the client (this option) + and the package instance (see the Instance Tag pkg_verify + attribute below) for the action to take place. + +install_action +^^^^^^^^^^^^^^ + +``install_action`` controls whether or not a package instance will be +installed if the package instance isn't installed. + +If install_action = install then the package instance is installed. +If install_action = none then the package instance is not installed. + +.. note:: + #. install_action must evaluate true for both the client (this + option) and the package instance (see the Instance Tag + install_action attribute below) for the action to take place. + +version_fail_action +^^^^^^^^^^^^^^^^^^^ + +``version_fail_action`` controls whether or not a package instance +will be updated if the installed package instance isn't the same +version as specified in the configuration. + +If version_fail_action = upgrade then the package instance is upgraded +(or downgraded). + +If version_fail_action = none then the package instance is not upgraded +(or downgraded). + +.. note:: + #. verion_fail_action must evaluate true for both the client (this + option) and the package instance (see the Instance Tag + version_fail_action attribute below) for the action to take + place. + +verify_fail_action +^^^^^^^^^^^^^^^^^^ + +``verify_fail_action`` controls whether or not a package instance will +be reinstalled if the installed package instance fails the Yum or RPM +verify. + +If verify_fail_action = reinstall then the package instance is reinstalled. +If verify_fail_action = none then the package instance is not reinstalled. + +.. note:: + #. verify_fail_action must evaluate true for both the client (this + option) and the package instance (see the Instance Tag + verify_fail_action attribute below) for the action to take + place. + #. The driver will not attempt to reinstall a package instance if + the only failure is a configuration file. + +Interactive Mode +---------------- + +Running the client in interactive mode (-I) prompts for the actions to +be taken as before. Prompts are per package and may apply to multiple +instances of that package. Each per package prompt will contain a list +of actions per instance. + +In the RPM driver, actions are encoded as: + +* D - Delete +* I - Install +* R - Reinstall +* U - Upgrade/Downgrade + +An example follows:: + + Install/Upgrade/delete Package aaa_base instance(s) - R(*:10.2-38.*) (y/N) + Install/Upgrade/delete Package evms instance(s) - R(*:2.5.5-67.*) (y/N) + Install/Upgrade/delete Package gpg-pubkey instance(s) - D(*:9c800aca-40d8063e.*) D(*:0dfb3188-41ed929b.*) D(*:7e2e3b05-44748aba.*) D(*:a1912208-446a0899.*) D(*:9c777da4-4515b5fd.*) D(*:307e3d54-44201d5d.*) (y/N) + Install/Upgrade/delete Package module-init-tools instance(s) - R(*:3.2.2-62.*) (y/N) + Install/Upgrade/delete Package multipath-tools instance(s) - R(*:0.4.7-29.*) (y/N) + Install/Upgrade/delete Package pam instance(s) - R(*:0.99.6.3-29.1.*) (y/N) + Install/Upgrade/delete Package perl-AppConfig instance(s) - U(None:1.52-4.noarch -> *:1.63-17.*) (y/N) + Install/Upgrade/delete Package postfix instance(s) - R(*:2.3.2-28.*) (y/N) + Install/Upgrade/delete Package sysconfig instance(s) - R(*:0.60.4-3.*) (y/N) + Install/Upgrade/delete Package udev instance(s) - R(*:103-12.*) (y/N) + +GPG Keys +-------- + +GPG is used by RPM to 'sign' packages. All vendor packages are signed +with the vendors GPG key. Additional signatures maybe added to the rpm +file at the users discretion. + +It is normal to have multiple GPG keys installed. For example, SLES10 +out of the box has six GPG keys installed. + +To the RPM database all GPG 'packages' have the name 'gpg-pubkey', which +may be nothing like the name of the file specified in the rpm -import +command. For example on Centos 4 the file name is RPM-GPG-KEY-centos4. +For SLES10 this means that there are six packages with the name +'gpg-pubkey' installed. + +RPM does not check GPG keys at package installation, while YUM does. + +RPM uses the rpm command for installation and does not therefore check +GPG signatures at package install time. RPM uses rpm-python for +verification and does by default do signature checks as part of the +client Inventory process. To do the signature check the appropriate +GPG keys must be installed. rpm-python is not very friendly if the +required key(s) is not installed (it crashes the client). + +The RPM driver detects, on a per package instance basis, if the +appropriate key is installed. If it is not, a warning message is +printed and the signature check is disabled for that package instance, +for that client run only. + +GPG keys can be installed and removed by the RPM driver. To install a +GPG key configure it in Pkgmgr/Rules as a package and add gpg-pubkey +to the clients abstract configuration. The gpg-pubkey package/instance +is treated as an install only package. gpg-pubkey packages are +installed by the RPM driver with the rpm -import command. + +gpg-pubkey packages will be removed by ``bcfg2 -r packages`` if they are +not in the clients configuration. + +Ignoring Files during Verification +---------------------------------- + +Ignore Tag +^^^^^^^^^^ + +The Ignore tag in Pkgmgr is used to "mask out" individual files from +the RPM verification. This is done by comparing the verification +failure results with the Ignore tag name. If there is a match, that +entry is not used by the client to determine if a package has failed +verification. + +Ignore tag entries can be specified at both the Package level, in which +case they apply to all Instances, and/or at the Instance level, in which +case they only apply to that instance. + +Ignore tag entries are used by the RPM driver. They can be specified +in both old and new style Pkgmgr files. + +The Ignore Tag supports the following attributes: + ++-----------+-------------+--------+ +| Attribute | Description | Values | ++===========+=============+========+ +| name | File name. | String | ++-----------+-------------+--------+ + +Example + +.. code-block:: xml + + <Package name='glibc' type='rpm'> + <Ignore name='/etc/rpc'/> + <Instance simplefile='glibc-2.3.4-2.25.x86_64.rpm' version='2.3.4' release='2.25' arch='x86_64'/> + </Package> + +POSIX 'ignore' Path entries +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The YUM analog to the Ignore Tag used by RPM is the use of Path +entries of type 'ignore'. The following shows an example for the +centos-release package which doesn't verify if you remove the default +repos and replace them with a custom repo. + +.. code-block:: xml + + <!-- Ignore verification failures for centos-release --> + <BoundPath name='/etc/yum.repos.d/CentOS-Base.repo' type='ignore'/> + <BoundPath name='/etc/yum.repos.d/CentOS-Media.repo' type='ignore'/> diff --git a/doc/client/tools/yumng.txt b/doc/client/tools/yumng.txt deleted file mode 100644 index 54003aea1..000000000 --- a/doc/client/tools/yumng.txt +++ /dev/null @@ -1,803 +0,0 @@ -.. -*- mode: rst -*- - -.. _client-tools-yumng: - -================================ -Bcfg2 RPMng/YUMng Client Drivers -================================ - -Introduction -============ - -The goal of this driver is to resolve the issues that exist with the -RPM and Yum client tool drivers. - -For the most part, the issues are due to RPM being able to have multiple -packages of the same name installed. This is an issue on all Red Hat -and SUSE based distributions. - -Examples of this are: - -* SLES10 and openSUSE 10.2 both install six GPG keys. From an RPM - perspective this means that there are six packages with the name - gpg-pubkey. -* YUM always installs, as opposed to upgrades, kernel packages. This is - hard coded in YUM (actually it can be overridden in yum.conf), - so systems using YUM will eventually have multiple kernel packages - installed. -* Red Hat family x86_64 based systems frequently have both an x86_64 - and an i386 version of the same package installed. - -The new Pkgmgr format files with Instances are therefore the only way to -accurately describe an RPM based system. It is recommended that all RPM -based systems be changed to use the new format configuration files and -the RPMng driver. Alternatively, you can use the newer :ref:`Packages -<server-plugins-generators-packages>` plugin. - -Development Status -================== - -Initial development of the drivers was done on Centos 4.4 x86_64, with -testing on openSUSE 10.2 x86_64. Centos has been tested with a new style -Pkgmgr file and openSUSE with an old style file (see the Configuration -section below for what this means). Testing has now moved to Centos 5 -x86_64 and old style files are no longer being tested. - -RPMng/YUMng are the default RPM drivers. - -Features -======== - -* Limited support for 0.9.4 and earlier Pkgmgr configuration files. See Configuration below for details. -* Full RPM package identification using epoch, version, release and arch. -* Support for multiple instances of packages with the Instance tag. -* Better control of the RPM verification using the pkg_checks, pkg_verify and verify_flags attributes. -* Support for install only packages such as the kernel packages. -* Support for per instance ignoring of individual files for the RPM verification with the Ignore tag. -* Multiple package Instances with full version information listed in interactive mode. -* Support for installation and removal of gpg-pubkey packages. -* Support for controlling what action is taken on package - verification failure with the install_action, version_fail_action and - verify_fail_action attributes. - - -RPMng Driver Overview -===================== - -The RPMng driver uses a mixture of rpm commands and rpm-python as detailed -in the sections below. - -rpmtools module ---------------- - -The rpmtools module conatins most of the rpm-python code and is imported -by RPMng.py and YUMng.py. - -RPMng.RefreshPackages() ------------------------ - -The RPMng.RefreshPackages method generates the installed dict using -rpm-python code from the rpmtools module. Full name, epoch, version, -release and arch information is stored. - -RPMng.VerifyPackages() ----------------------- - -The RPMng.VerifyPackages method generates a number of structures that -record the state of the of the system compared to the Bcfg2 literal -configuration retrieved from the server. These structures are mainly -used by the RPMng.Install method. - -AS part of the verification process an rpm package level verification is -carried out using rpm-python code from the rpmtools module. Full details -of the failures are returned in a complicated dict/list structure for -later use. - -RPMng.Install() ---------------- - -The RPMng.Install method attempts to fix what the RPMng.VerifyPackages -method found wrong. It does this by installing, reinstalling, deleting -and upgrading RPMs. RPMng.Install does not use rpm-python. It does use -the following rppm commands as appropriate:: - - rpm -install - - rpm --import - - rpm -upgrade - -A method (RPMng.to reinstall_check()) to decide whether to do a reinstall -of a package instance or not has been added, but is very simple at -this stage. Currently it will prevent a reinstall if the only reason -for a verification failure was due to an RPM configuration (%config) -file. A package reinstall will not replace these, so there is no point -reinstalling. - -RPMng.Remove() --------------- - -The RPMng.Remove method is written using rpm-python code in the rpmtools -module. Full nevra information is used in the selection of the package -removal. - -Installation -============ - -isprelink ---------- - -This is a Python C extension module that checks to see if a file has -been prelinked or not. It should be built and installed on systems that -have the prelink package installed (only Red Hat family systems as far -as I can tell). rpmtools will function without the isprelink module, -but performance is not good. - -Source can be found here ftp://ftp.mcs.anl.gov/pub/bcfg/isprelink-0.1.2.tar.gz - -To compile and install prelink, execute:: - - python setup.py install - -in the rpmtools directory. The elfutils-libelf-devel package is required -for the compilation. - -There are Centos x86_64 RPMs here -ftp://ftp.mcs.anl.gov/pub/bcfg/archive/redhat/ - -Configuration and Usage -======================= - -Loading of RPMng ----------------- - -The RPMng driver can be loaded by command line options, client -configuration file options or as the default driver for RPM packages. - -From the command line:: - - bcfg2 -n -v -d -D Action,POSIX,Chkconfig,RPMng - -This produces quite a bit of output so you may want to redirect the -output to a file for review. - -In the ``bcfg2.conf`` file:: - - [client] - #drivers = Action,Chkconfig,POSIX,YUMng - drivers = Action,Chkconfig,POSIX,RPMng - -.. note:: Note that loading this driver will unload the RPM driver, so the Yum driver will not work. - -Configuration File Options --------------------------- - -A number of paramters can be set in the client configuration for both -the RPMng and YUMng drivers. Each driver has its own section. A full -client configuration file with all the options specified is below:: - - [communication] - protocol = xmlrpc/ssl - password = xxxxxx - user = yyyyyyy - - [components] - bcfg2 = https://bcfg2:6789 - - [client] - #drivers = Action,Chkconfig,POSIX,YUMng - drivers = Action,Chkconfig,POSIX,RPMng - - [RPMng] - pkg_checks = true - pkg_verify = true - erase_flags = allmatches - installonlypackages = kernel, kernel-bigmem, kernel-enterprise, kernel-smp, kernel-modules, kernel-debug, kernel-unsupported, kernel-source, kernel-devel, kernel-default, kernel-largesmp-devel, kernel-largesmp, kernel-xen, gpg-pubkey - install_action = install - version_fail_action = upgrade - verify_fail_action = reinstall - - [YUMng] - pkg_checks = True - pkg_verify = true - erase_flags = allmatches - autodep = true - installonlypackages = kernel, kernel-bigmem, kernel-enterprise, kernel-smp, kernel-modules, kernel-debug, kernel-unsupported, kernel-source, kernel-devel, kernel-default, kernel-largesmp-devel, kernel-largesmp, kernel-xen, gpg-pubkey - install_action = install - version_fail_action = upgrade - verify_fail_action = reinstall - -installOnlyPkgs -^^^^^^^^^^^^^^^ - -Install only packages are packages that should only ever be installed -or deleted, not upgraded. - -The only packages for which this is an absolute on, are the gpg-pubkey -packages. It is however 'best' practice to only ever install/delete -kernel packages. The wisdom being that the package for the currently -running kernel should always be installed. Doing an upgrade would delete -the running kernel package. - -The RPMng driver follows the YUM practice of having a list of install -only packages. A default list is hard coded in RPMng.py. This maybe over -ridden in the client configuration file. - -Note that except for gpg-pubkey packages (which are always added to the -list by the driver) the list in the client configuration file completely -replaces the default list. An empty list means that there are no install -only packages (except for gpg-pubkey), which is the behaviour of the -old RPM driver. - -Example - an empty list:: - - [RPMng] - installonlypackages = - -Example - The default list:: - - [RPMng] - installonlypackages = kernel, kernel-bigmem, kernel-enterprise, kernel-smp, kernel-modules, kernel-debug, kernel-unsupported, kernel-source, kernel-devel, kernel-default, kernel-largesmp-devel, kernel-largesmp, kernel-xen, gpg-pubkey - -erase_flags -^^^^^^^^^^^ - -erase_flags are rpm options used by 'rpm -erase' in the client Remove() -method. The RPMng erase is written using rpm-python and does not use -the rpm command. - -The erase flags are specified in the client configuration file as a comma -separated list and apply to all RPM erase operations. The default is:: - - [RPMng] - erase_flags = allmatches - -The following rpm erase options are supported, see the rpm man page -for details.:: - - noscripts - notriggers - repackage - allmatches - nodeps - -.. note:: Note that specifying erase_flags in the configuration file completely replaces the default. - -pkg_checks -^^^^^^^^^^ - -The RPMng/YUMng drivers do the following three checks/status: - -#. Installed -#. Version -#. rpm verify - -Setting pkg_checks = true (the default) in the client configuration file -means that all three checks will be done for all packages. - -Setting pkg_checks = false in the client configuration file means that -only the Installed check will be done for all packages. - -The true/false value can be any combination of upper and lower case. - -.. note:: - #. pkg_checks must evaluate true for both the client (this option) and the package (see the Package Tag pkg_checks attribute below) for the action to take place. - #. If pkg_checks = false then the Pkgmgr entries do not need the version information. See the examples towards the bottom of the page. - -pkg_verify -^^^^^^^^^^ - -The RPMng/YUMng drivers do the following three checks/status: - -#. Installed -#. Version -#. rpm verify - -Setting pkg_verify = true (the default) in the client configuration -file means that all three checks will be done for all packages as long -as pkg_checks = true. - -Setting pkg_verify = false in the client configuration file means that -the rpm verify wil not be done for all packages on the client. - -The true/false value can be any combination of upper and lower case. - -.. note:: - #. pkg_verify must evaluate true for both the client (this option) and the package instance (see the Instance Tag pkg_verify attribute below) for the action to take place. - -install_action -^^^^^^^^^^^^^^ - -The RPMng/YUMng drivers do the following three checks/status: - -#. Installed -#. Version -#. rpm verify - -install_action controls whether or not a package instance will be -installed if the installed check fails (i.e. if the package instance -isn't installed). - -If install_action = install then the package instance is installed. -If install_action = none then the package instance is not installed. - -.. note:: - #. install_action must evaluate true for both the client (this option) and the package instance (see the Instance Tag install_action attribute below) for the action to take place. - -version_fail_action -^^^^^^^^^^^^^^^^^^^ - -The RPMng/YUMng drivers do the following three checks/status: - -#. Installed -#. Version -#. rpm verify - -version_fail_action controls whether or not a package instance will -be updated if the version check fails (i.e. if the installed package -instance isn't the same version as specified in the configuration). - -If version_fail_action = upgrade then the package instance is upgraded -(or downgraded). - -If version_fail_action = none then the package instance is not upgraded -(or downgraded). - -.. note:: - #. verion_fail_action must evaluate true for both the client (this option) and the package instance (see the Instance Tag version_fail_action attribute below) for the action to take place. - -verify_fail_action -^^^^^^^^^^^^^^^^^^ - -The RPMng/YUMng drivers do the following three checks/status: - -#. Installed -#. Version -#. rpm verify - -verify_fail_action controls whether or not a package instance will be -reinstlled if the version check fails (i.e. if the installed package -instance isn't the same version as specified in the configuration). - -If verify_fail_action = reinstall then the package instance is reinstalled. -If verify_fail_action = none then the package instance is not reinstalled. - -.. note:: - #. verify_fail_action must evaluate true for both the client (this option) and the package instance (see the Instance Tag verify_fail_action attribute below) for the action to take place. - #. yum cannot reinstall packages, so this option is really only relevant to RPMng. - #. RPMng will not attempt to reinstall a package instance if the only failure is an RPM configuration file. - #. RPMng will not attempt to reinstall a package instance if the only failure is an RPM dependency failure. - -Interactive Mode ----------------- - -Running the client in interactive mode (-I) prompts for the actions to -be taken as before. Prompts are per package and may apply to multiple -instances of that package. Each per package prompt will contain a list -of actions per instance. - -Actions are encoded as - -D - Delete - -I - Install - -R - Reinstall - -U - Upgrade/Downgrade - - -An example is below. The example is from a system that is still using -the old Pkgmgr format, so the epoch and arch appear as '*'.:: - - Install/Upgrade/delete Package aaa_base instance(s) - R(*:10.2-38.*) (y/N) - Install/Upgrade/delete Package evms instance(s) - R(*:2.5.5-67.*) (y/N) - Install/Upgrade/delete Package gpg-pubkey instance(s) - D(*:9c800aca-40d8063e.*) D(*:0dfb3188-41ed929b.*) D(*:7e2e3b05-44748aba.*) D(*:a1912208-446a0899.*) D(*:9c777da4-4515b5fd.*) D(*:307e3d54-44201d5d.*) (y/N) - Install/Upgrade/delete Package module-init-tools instance(s) - R(*:3.2.2-62.*) (y/N) - Install/Upgrade/delete Package multipath-tools instance(s) - R(*:0.4.7-29.*) (y/N) - Install/Upgrade/delete Package pam instance(s) - R(*:0.99.6.3-29.1.*) (y/N) - Install/Upgrade/delete Package perl-AppConfig instance(s) - U(None:1.52-4.noarch -> *:1.63-17.*) (y/N) - Install/Upgrade/delete Package postfix instance(s) - R(*:2.3.2-28.*) (y/N) - Install/Upgrade/delete Package sysconfig instance(s) - R(*:0.60.4-3.*) (y/N) - Install/Upgrade/delete Package udev instance(s) - R(*:103-12.*) (y/N) - -GPG Keys --------- - -GPG is used by RPM to 'sign' packages. All vendor packages are signed -with the vendors GPG key. Additional signatures maybe added to the rpm -file at the users discretion. - -It is normal to have multiple GPG keys installed. For example, SLES10 -out of the box has six GPG keys installed. - -To the RPM database all GPG 'packages' have the name 'gpg-pubkey', which -may be nothing like the name of the file specified in the rpm -import -command. For example on Centos 4 the file name is RPM-GPG-KEY-centos4. -For SLES10 this means that there are six packages with the name -'gpg-pubkey' installed. - -RPM does not check GPG keys at package installation, YUM does. - -RPMng uses the rpm command for installation and does not therefore -check GPG signatures at package install time. RPMng uses rpm-python -for verification and does by default do signature checks as part of the -client Inventory process. To do the signature check the appropriate GPG -keys must be installed. rpm-python is not very friendly if the required -key(s) is not installed (it crashes the client). - -The RPMng driver detects, on a per package instance basis, if the -appropriate key is installed. If it is not, a warning message is printed -and the signature check is disabled for that package instance, for that -client run only. - -GPG keys can be installed and removed by the RPMng driver. To install a -GPG key configure it in Pkgmgr/Rules as a package and add gpg-pubkey to -the clients abstract configuration. The gpg-pubkey package/instance is -treated as an install only package. gpg-pubkey packages are installed -by the RPMng driver with the rpm -import command. - -gpg-pubkey packages will be removed by ``bcfg2 -r packages`` if they are -not in the clients configuration. - -.. code-block:: xml - - <PackageList uri='http://fortress/' priority='0' type='rpm'> - <Group name='Centos4.4-Standard'> - <Group name='x86_64'> - <Package name='gpg-pubkey' type='rpm'> - <Instance simplefile='mrepo/Centos44-x86_64/disc1/RPM-GPG-KEY-centos4' version='443e1821' release='421f218f'/> - <Instance simplefile='RPM-GPG-KEY-mbrady' version='9c777da4' release='4515b5fd'/> - </Package> - </Group> - </Group> - </PackageList> - -Example gpg-pubkey Pkgmgr configuration file. - -Pkgmgr Configuration --------------------- - -Also see the general :ref:`Pkgmgr <server-plugins-generators-pkgmgr>` -and :ref:`server-plugins-structures-altsrc` pages. - -Package Tag (Old style) -^^^^^^^^^^^^^^^^^^^^^^^ - -Old style (meaning no Instance tag) Pkgmgr files have limited support. -Specifically the multiarch and verify attributes are ignored. - -If multiarch type support is needed a new style format file must be used. - -If some control over the verification is needed, replace the verify -attribute with the pkg_checks or verify_flags attributes. The pkg_checks -and verify_flags attributes are detailed under the Instance tag heading. - -Package Tag (New Style) and Attributes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The new style package tag supports the name and pkg_checks attributes -and requires the use of Instance tag entries. - -New style configuration files must be generated from the RPM headers. -Either from RPM files or from the RPM DB. - -The included pkgmgr_gen.py can be used as a starting point for generating -configuration files from directories of RPM package files. pkgmgr_gen.py ---help for the options. - -The included pkgmgr_update.py can be used to update the package instance -versions in configuration files from directories of package files. -pkgmgr_update.py --help for the options. - -+------------+---------------------------------------+------------------------+ -| Attribute | Description | Values | -+============+=======================================+========================+ -| name | Package name. | String | -+------------+---------------------------------------+------------------------+ -| pkg_checks | Do the version and rpm verify checks. | true(default) or false | -+------------+---------------------------------------+------------------------+ - -Instance Tag and Attributes -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The instance tag supports the following attributes: - -+---------------------+----------------------------+--------------------------+ -| Attribute | Description | Values | -+=====================+============================+==========================+ -| simplefile | Package file name. | String (see Notes below) | -+---------------------+----------------------------+--------------------------+ -| epoch | Package epoch. | String (numeric only) | -| | | (optional) | -+---------------------+----------------------------+--------------------------+ -| version | Package version. | String | -+---------------------+----------------------------+--------------------------+ -| release | Package release. | String | -+---------------------+----------------------------+--------------------------+ -| arch | Package architecture. | Architecture String e.g. | -| | | (i386|i586|i686|x86_64) | -+---------------------+----------------------------+--------------------------+ -| verify_flags | Comma separated list of | nodeps, nodigest, | -| | rpm --verify options. See | nofiles, noscripts, | -| | the rpm man page for their | nosignature, nolinkto, | -| | details. | nomd5, nosize, nouser, | -| | | nogroup, nomtime, | -| | | nomode, nordev | -+---------------------+----------------------------+--------------------------+ -| pkg_verify | Do the rpm verify | true(default) or false | -+---------------------+----------------------------+--------------------------+ -| install_action | Install package instance | install(default) or none | -| | if it is not installed. | | -+---------------------+----------------------------+--------------------------+ -| version_fail_action | Upgrade package if the | upgrade(default) or none | -| | incorrect version is | | -| | installed. | | -+---------------------+----------------------------+--------------------------+ -| verify_fail_action | Reinstall the package | reinstall(default) or | -| | instance if the rpm verify | none | -| | failed | | -+---------------------+----------------------------+--------------------------+ - -.. note:: - - The simplefile attribute doesn't need to be just the filename, - meaning the basename. It is joined with the uri attribute from the - PackageList Tag to form the URL that the client will use to download - the package. So the uri could just be the host portion of the url - and simple file could be the directory path. - - e.g. - - .. code-block:: xml - - <PackageList uri='http://fortress/' priority='0' type='rpm'> - <Group name='Centos4.4-Standard'> - <Group name='x86_64'> - <Package name='gpg-pubkey' type='rpm'> - <Instance simplefile='mrepo/Centos44-x86_64/disc1/RPM-GPG-KEY-centos4' version='443e1821' release='421f218f'/> - <Instance simplefile='RPM-GPG-KEY-mbrady' version='9c777da4' release='4515b5fd'/> - </Package> - </Group> - </Group> - </PackageList> - -The values for epoch, version, release and arch attributes must come -from the RPM header, not the RPM file name. - -Epoch is a strange thing. In short:: - - epoch not set == epoch=None < epoch='0' < epoch='1' - -and it is an int, but elementtree attributes have to be str or unicode, -so the driver is constantly converting. - -Ignore Tag -^^^^^^^^^^ - -The Ignore tag is used to "mask out" individual files from the RPM -verification. This is done by comparing the verification failure results -with the Ignore tag name. If there is a match, that entry is not used -by the client to determine if a package has failed verification. - -Ignore tag entries can be specified at both the Package level, in which -case they apply to all Instances, and/or at the Instance level, in which -case they only apply to that instance. - -Ignore tag entries are used by the RPMng driver. They can be specified -in both old and new style Pkgmgr files. - -The Ignore Tag supports the following attributes: - -+-----------+-------------+--------+ -| Attribute | Description | Values | -+===========+=============+========+ -| name | File name. | String | -+-----------+-------------+--------+ - -Example - -.. code-block:: xml - - <Package name='glibc' type='rpm'> - <Ignore name='/etc/rpc'/> - <Instance simplefile='glibc-2.3.4-2.25.x86_64.rpm' version='2.3.4' release='2.25' arch='x86_64'/> - </Package> - -POSIX 'ignore' Path entries -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The YUMng analog to the Ignore Tag used by RPMng is the use of Path -entries of type 'ignore'. The following shows an example for the -centos-release package which doesn't verify if you remove the default -repos and replace them with a custom repo. - -.. code-block:: xml - - <!-- Ignore verification failures for centos-release --> - <BoundPath name='/etc/yum.repos.d/CentOS-Base.repo' type='ignore'/> - <BoundPath name='/etc/yum.repos.d/CentOS-Media.repo' type='ignore'/> - -Automated Generation of Pkgmgr Configuration Files --------------------------------------------------- - -The two utilities detailed below are provided in the tools directory of -the source tarball. - -Also see the general :ref:`Pkgmgr <server-plugins-generators-pkgmgr>` -and :ref:`server-plugins-structures-altsrc` pages. - -pkgmgr_gen.py -^^^^^^^^^^^^^ - -pkgmgr_gen will generate a Pkgmgr file from a list of directories -containing RPMs or from a list of YUM repositories.:: - - [root@bcfg2 Pkgmgr]# pkgmgr_gen.py --help usage: pkgmgr_gen.py - [options] - - options: - -h, --help show this help message and exit - -aARCHS, --archs=ARCHS - Comma separated list of subarchitectures to include. - The highest subarichitecture required in an - architecture group should specified. Lower - subarchitecture packages will be loaded if that - is all that is available. e.g. The higher of i386, - i486 and i586 packages will be loaded if -a i586 - is specified. (Default: all). - -dRPMDIRS, --rpmdirs=RPMDIRS - Comma separated list of directories to scan for RPMS. - Wilcards are permitted. - -eENDDATE, --enddate=ENDDATE - End date for RPM file selection. - -fFORMAT, --format=FORMAT - Format of the Output. Choices are yum or rpm. - (Default: yum) - -gGROUPS, --groups=GROUPS - List of comma separated groups to nest Package - entities in. - -iINDENT, --indent=INDENT - Number of leading spaces to indent nested entries in - the output. - (Default:4) - -oOUTFILE, --outfile=OUTFILE - Output file name. - -P, --pkgmgrhdr Include PackageList header in output. - -pPRIORITY, --priority=PRIORITY - Value to set priority attribute in the PackageList Tag. - (Default: 0) - -rRELEASE, --release=RELEASE - Which releases to include in the output. Choices are - all or latest. (Default: latest). - -sSTARTDATE, --startdate=STARTDATE - Start date for RPM file selection. - -uURI, --uri=URI URI for PackageList header required for RPM format - ouput. - -v, --verbose Enable verbose output. - -yYUMREPOS, --yumrepos=YUMREPOS - Comma separated list of YUM repository URLs to load. - NOTE: Each URL must end in a '/' character. - -.. note:: The startdate and enddate options are not yet implemented. - -pkgmgr_update.py ----------------- - -pkgmgr_update will update the release (meaning the epoch, version -and release) information in an existing Pkgrmgr file from a list of -directories containing RPMs or from a list of YUM repositories. All Tags -and other attributes in the existing file will remain unchanged.:: - - [root@bcfg2 Pkgmgr]# pkgmgr_update.py --help - usage: pkgmgr_update.py [options] - - options: - -h, --help show this help message and exit - -cCONFIGFILE, --configfile=CONFIGFILE - Existing Pkgmgr configuration file name. - -dRPMDIRS, --rpmdirs=RPMDIRS - Comma separated list of directories to scan for RPMS. - Wilcards are permitted. - -oOUTFILE, --outfile=OUTFILE - Output file name or new Pkgrmgr file. - -v, --verbose Enable verbose output. - -yYUMREPOS, --yumrepos=YUMREPOS - Comma separated list of YUM repository URLs to load. - NOTE: Each URL must end in a '/' character. - -Pkgmgr Configuration Examples ------------------------------ - -verify_flags -^^^^^^^^^^^^ - -This entry was used for the Centos test client used during RPMng -development. - -.. code-block:: xml - - <Package name='bcfg2' type='rpm'> - <Instance simplefile='bcfg2-0.9.3-0.0pre5.noarch.rpm' version='0.9.3' release='0.0pre5' arch='noarch' verify_flags='nomd5,nosize,nomtime'/> - </Package> - -Multiple Instances -^^^^^^^^^^^^^^^^^^ - -.. code-block:: xml - - <Package name='beecrypt' type='rpm'> - <Instance simplefile='beecrypt-3.1.0-6.x86_64.rpm' version='3.1.0' release='6' arch='x86_64'/> - <Instance simplefile='beecrypt-3.1.0-6.i386.rpm' version='3.1.0' release='6' arch='i386'/> - </Package> - -Kernel -^^^^^^ - -.. note:: Multiple instances with the same architecture must be in the installOnlyPkgs list. - -.. code-block:: xml - - <Package name='kernel' type='rpm'> - <Instance simplefile='kernel-2.6.9-42.0.8.EL.x86_64.rpm' version='2.6.9' release='42.0.8.EL' arch='x86_64'/> - <Instance simplefile='kernel-2.6.9-42.0.10.EL.x86_64.rpm' version='2.6.9' release='42.0.10.EL' arch='x86_64'/> - </Package> - -Per Instance Ignore -^^^^^^^^^^^^^^^^^^^ - -.. note:: - - In this case a per instance ignore is actually a bad idea as the - verify failure is because of multiarch issues where the last package - installed wins. So this would be better as a Package level ignore. - -Ignore tag entries only work with the RPMng driver. They do not appear -to be supported in YUMng as of 1.0pre5. - -.. code-block:: xml - - <Package name='glibc' type='rpm'> - <Instance simplefile='glibc-2.3.4-2.25.x86_64.rpm' version='2.3.4' release='2.25' arch='x86_64'> - <Ignore name='/etc/rpc'/> - </Instance> - <Instance simplefile='glibc-2.3.4-2.25.i686.rpm' version='2.3.4' release='2.25' arch='i686'/> - </Package> - -pkg_checks -^^^^^^^^^^ - -If pkg_checks = false the version information is not required. If -pkg_checks = true the full information is needed as normal. - -For YUMng a minimal entry is - -.. code-block:: xml - - <Package name="bcfg2" type="yum" pkg_checks="False"/> - -In fact for YUMng, with pkg_checks = false, any combination of the nevra -attributes that will build a valid yum package name (see the Misc heading -on the yum man page) is valid. - -.. code-block:: xml - - <Package name="bcfg2" type="yum" pkg_checks="False" arch="x86_64"/> - -For RPMng a minimal entry is - -.. code-block:: xml - - <Package name="bcfg2" type="rpm" pkg_checks="False" simplefile="bcfg2-0.9.4-0.0pre1.noarch.rpm"/> - -verify_fail_action -^^^^^^^^^^^^^^^^^^ - -The way I have Bcfg2 configured for my development systems. This way -it reports bad, but doesn't do anything about it. - -.. code-block:: xml - - <Package name='bcfg2' type='rpm'> - <Instance simplefile='bcfg2-0.9.3-0.0pre5.noarch.rpm' version='0.9.3' release='0.0pre5' arch='noarch' verify_fail_action='none'/> - </Package> diff --git a/doc/server/plugins/generators/pkgmgr.txt b/doc/server/plugins/generators/pkgmgr.txt index d3342feb6..76a8ec1a5 100644 --- a/doc/server/plugins/generators/pkgmgr.txt +++ b/doc/server/plugins/generators/pkgmgr.txt @@ -6,14 +6,6 @@ Pkgmgr ====== -.. note:: - - See [wiki:ClientTools/RPMng#PackageTagNewStyleandAttributes - RPMng#PackageTagNewStyleandAttributes].''' The way of showing the - architecture of the RPM has changed. The new way is "arch". The - old way is "multiarch". '''This document needs to be updated and - include version of Bcfg2 where change took place.''' - The Pkgmgr plugin resolves the Abstract Configuration Entity "Package" to a package specification that the client can use to detect, verify and install the specified package. @@ -48,32 +40,60 @@ Group membership may be negated. Tag Attributes in Pkgmgr ======================== -PackageList Tag ---------------- - -The PackageList Tag may have the following attributes: - -+-----------+-----------------------------------------------+----------------+ -| Name | Description | Values | -+===========+===============================================+================+ -| priority | Sets the priority for packages in the package | Integer | -| | list. The higher value wins. | | -+-----------+-----------------------------------------------+----------------+ -| type | Package type that applies to all packages in | deb|rpm|blast| | -| | the list. This value is inherited by all | encap|sysv| | -| | packages without an explicit type attribute. | portage|yum | -+-----------+-----------------------------------------------+----------------+ -| uri | URI to prepend to filename sto fetch packages | String | -| | in this list. | | -+-----------+-----------------------------------------------+----------------+ -| multiarch | Comma-separated list of architectures that | String | -| | apply to all packages in this list. Inherited | | -| | by all package entries in the file that does | | -| | not have this attribute explicitly. | | -+-----------+-----------------------------------------------+----------------+ -| srcs | To be used with multiarch support. Inherited | String | -| | by all Package entries without this attribute | | -+-----------+-----------------------------------------------+----------------+ +Package Tag +^^^^^^^^^^^ + +The package tag supports the name and pkg_checks attributes and +requires the use of Instance tag entries. + ++------------+---------------------------------------+------------------------+ +| Attribute | Description | Values | ++============+=======================================+========================+ +| name | Package name. | String | ++------------+---------------------------------------+------------------------+ +| pkg_checks | Do the version and rpm verify checks. | true(default) or false | ++------------+---------------------------------------+------------------------+ + +Instance Tag and Attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The instance tag supports the following attributes: + ++---------------------+----------------------------+--------------------------+ +| Attribute | Description | Values | ++=====================+============================+==========================+ +| simplefile | Package file name. | String (see Notes below) | ++---------------------+----------------------------+--------------------------+ +| epoch | Package epoch. | String (numeric only) | +| | | (optional) | ++---------------------+----------------------------+--------------------------+ +| version | Package version. | String | ++---------------------+----------------------------+--------------------------+ +| release | Package release. | String | ++---------------------+----------------------------+--------------------------+ +| arch | Package architecture. | Architecture String e.g. | +| | | (i386|i586|i686|x86_64) | ++---------------------+----------------------------+--------------------------+ +| verify_flags | Comma separated list of | nodeps, nodigest, | +| | rpm --verify options. See | nofiles, noscripts, | +| | the rpm man page for their | nosignature, nolinkto, | +| | details. | nomd5, nosize, nouser, | +| | | nogroup, nomtime, | +| | | nomode, nordev | ++---------------------+----------------------------+--------------------------+ +| pkg_verify | Do the rpm verify | true(default) or false | ++---------------------+----------------------------+--------------------------+ +| install_action | Install package instance | install(default) or none | +| | if it is not installed. | | ++---------------------+----------------------------+--------------------------+ +| version_fail_action | Upgrade package if the | upgrade(default) or none | +| | incorrect version is | | +| | installed. | | ++---------------------+----------------------------+--------------------------+ +| verify_fail_action | Reinstall the package | reinstall(default) or | +| | instance if the rpm verify | none | +| | failed | | ++---------------------+----------------------------+--------------------------+ Pkgmgr Group Tag ---------------- @@ -88,40 +108,6 @@ The Pkgmgr Group Tag may have the following attributes: | negate | Negate group membership (is not a member of) | True|False | +--------+----------------------------------------------+------------+ -Package Tag ------------ - -The Package Tag may have the following attributes: - -+------------+----------------------------------------------+------------+ -| Name | Description | Values | -+============+==============================================+============+ -| name | Package Name | String | -+------------+----------------------------------------------+------------+ -| version | Package version, set to ``auto`` to install | String | -| | the latest version in the client's cache, or | | -| | ``any`` to verify that any version of the | | -| | package is installed on the client | | -+------------+----------------------------------------------+------------+ -| file | Package file name. Several other attributes | String | -| | (name, version) can be automatically defined | | -| | based on regular expressions defined in the | | -| | Pkgmgr plugin. | | -+------------+----------------------------------------------+------------+ -| simplefile | Package file name. No name parsing is | String | -| | performed, so no extra fields get set | | -+------------+----------------------------------------------+------------+ -| verify | Whether package verification should be done | True|False | -+------------+----------------------------------------------+------------+ -| multiarch | Comma-separated list of the architectures of | String | -| | this package that should be installed. | | -| | (Temporary work-around) | | -+------------+----------------------------------------------+------------+ -| srcs | File name creation rules for multiarch | String | -| | packages. (Temporary work-around) | | -+------------+----------------------------------------------+------------+ -| type | Package type (rpm, yum, apt, sysv, blast) | String | -+------------+----------------------------------------------+------------+ Client Tag ---------- @@ -323,11 +309,186 @@ implemented that should ease this situation considerably. Altogether, this should move policy decisions about package architectures to bundles/base. -Client Driver (Plugins) Specific Attributes -=========================================== +Automated Generation of Pkgmgr Configuration Files +-------------------------------------------------- + +The two utilities detailed below are provided in the tools directory of +the source tarball. + +Also see the general :ref:`Pkgmgr <server-plugins-generators-pkgmgr>` +and :ref:`server-plugins-structures-altsrc` pages. + +pkgmgr_gen.py +^^^^^^^^^^^^^ + +pkgmgr_gen will generate a Pkgmgr file from a list of directories +containing RPMs or from a list of YUM repositories.:: + + [root@bcfg2 Pkgmgr]# pkgmgr_gen.py --help usage: pkgmgr_gen.py + [options] + + options: + -h, --help show this help message and exit + -aARCHS, --archs=ARCHS + Comma separated list of subarchitectures to include. + The highest subarichitecture required in an + architecture group should specified. Lower + subarchitecture packages will be loaded if that + is all that is available. e.g. The higher of i386, + i486 and i586 packages will be loaded if -a i586 + is specified. (Default: all). + -dRPMDIRS, --rpmdirs=RPMDIRS + Comma separated list of directories to scan for RPMS. + Wilcards are permitted. + -eENDDATE, --enddate=ENDDATE + End date for RPM file selection. + -fFORMAT, --format=FORMAT + Format of the Output. Choices are yum or rpm. + (Default: yum) + -gGROUPS, --groups=GROUPS + List of comma separated groups to nest Package + entities in. + -iINDENT, --indent=INDENT + Number of leading spaces to indent nested entries in + the output. + (Default:4) + -oOUTFILE, --outfile=OUTFILE + Output file name. + -P, --pkgmgrhdr Include PackageList header in output. + -pPRIORITY, --priority=PRIORITY + Value to set priority attribute in the PackageList Tag. + (Default: 0) + -rRELEASE, --release=RELEASE + Which releases to include in the output. Choices are + all or latest. (Default: latest). + -sSTARTDATE, --startdate=STARTDATE + Start date for RPM file selection. + -uURI, --uri=URI URI for PackageList header required for RPM format + ouput. + -v, --verbose Enable verbose output. + -yYUMREPOS, --yumrepos=YUMREPOS + Comma separated list of YUM repository URLs to load. + NOTE: Each URL must end in a '/' character. + +.. note:: The startdate and enddate options are not yet implemented. + +pkgmgr_update.py +---------------- + +pkgmgr_update will update the release (meaning the epoch, version +and release) information in an existing Pkgrmgr file from a list of +directories containing RPMs or from a list of YUM repositories. All Tags +and other attributes in the existing file will remain unchanged.:: + + [root@bcfg2 Pkgmgr]# pkgmgr_update.py --help + usage: pkgmgr_update.py [options] + + options: + -h, --help show this help message and exit + -cCONFIGFILE, --configfile=CONFIGFILE + Existing Pkgmgr configuration file name. + -dRPMDIRS, --rpmdirs=RPMDIRS + Comma separated list of directories to scan for RPMS. + Wilcards are permitted. + -oOUTFILE, --outfile=OUTFILE + Output file name or new Pkgrmgr file. + -v, --verbose Enable verbose output. + -yYUMREPOS, --yumrepos=YUMREPOS + Comma separated list of YUM repository URLs to load. + NOTE: Each URL must end in a '/' character. + +Pkgmgr Configuration Examples +----------------------------- + +verify_flags +^^^^^^^^^^^^ + +This entry was used for the Centos test client used during RPM +development. + +.. code-block:: xml + + <Package name='bcfg2' type='rpm'> + <Instance simplefile='bcfg2-0.9.3-0.0pre5.noarch.rpm' version='0.9.3' release='0.0pre5' arch='noarch' verify_flags='nomd5,nosize,nomtime'/> + </Package> + +Multiple Instances +^^^^^^^^^^^^^^^^^^ + +.. code-block:: xml + + <Package name='beecrypt' type='rpm'> + <Instance simplefile='beecrypt-3.1.0-6.x86_64.rpm' version='3.1.0' release='6' arch='x86_64'/> + <Instance simplefile='beecrypt-3.1.0-6.i386.rpm' version='3.1.0' release='6' arch='i386'/> + </Package> + +Kernel +^^^^^^ + +.. note:: Multiple instances with the same architecture must be in the installOnlyPkgs list. + +.. code-block:: xml + + <Package name='kernel' type='rpm'> + <Instance simplefile='kernel-2.6.9-42.0.8.EL.x86_64.rpm' version='2.6.9' release='42.0.8.EL' arch='x86_64'/> + <Instance simplefile='kernel-2.6.9-42.0.10.EL.x86_64.rpm' version='2.6.9' release='42.0.10.EL' arch='x86_64'/> + </Package> + +Per Instance Ignore +^^^^^^^^^^^^^^^^^^^ + +.. note:: -Not all the attributes that are available in Pkgmgr are honoured by all -the client drivers. The following client drivers (plugins) have driver -specific attributes: + In this case a per instance ignore is actually a bad idea as the + verify failure is because of multiarch issues where the last package + installed wins. So this would be better as a Package level ignore. -* :ref:`RPMng <client-tools-yumng>` +Ignore tag entries only work with the RPM driver. They do not appear +to be supported in YUM as of 1.0pre5. + +.. code-block:: xml + + <Package name='glibc' type='rpm'> + <Instance simplefile='glibc-2.3.4-2.25.x86_64.rpm' version='2.3.4' release='2.25' arch='x86_64'> + <Ignore name='/etc/rpc'/> + </Instance> + <Instance simplefile='glibc-2.3.4-2.25.i686.rpm' version='2.3.4' release='2.25' arch='i686'/> + </Package> + +pkg_checks +^^^^^^^^^^ + +If pkg_checks = false the version information is not required. If +pkg_checks = true the full information is needed as normal. + +For YUM a minimal entry is + +.. code-block:: xml + + <Package name="bcfg2" type="yum" pkg_checks="False"/> + +In fact for YUM, with pkg_checks = false, any combination of the nevra +attributes that will build a valid yum package name (see the Misc heading +on the yum man page) is valid. + +.. code-block:: xml + + <Package name="bcfg2" type="yum" pkg_checks="False" arch="x86_64"/> + +For RPM a minimal entry is + +.. code-block:: xml + + <Package name="bcfg2" type="rpm" pkg_checks="False" simplefile="bcfg2-0.9.4-0.0pre1.noarch.rpm"/> + +verify_fail_action +^^^^^^^^^^^^^^^^^^ + +The way I have Bcfg2 configured for my development systems. This way +it reports bad, but doesn't do anything about it. + +.. code-block:: xml + + <Package name='bcfg2' type='rpm'> + <Instance simplefile='bcfg2-0.9.3-0.0pre5.noarch.rpm' version='0.9.3' release='0.0pre5' arch='noarch' verify_fail_action='none'/> + </Package> diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index ef61940eb..b0032057a 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -84,11 +84,11 @@ class Frame(object): for tool in list(tclass.values()): try: self.tools.append(tool(self.logger, setup, config)) - except Bcfg2.Client.Tools.toolInstantiationError: + except Bcfg2.Client.Tools.ToolInstantiationError: continue except: - self.logger.error("Failed to instantiate tool %s" % \ - (tool), exc_info=1) + self.logger.error("Failed to instantiate tool %s" % tool, + exc_info=1) for tool in self.tools[:]: for conflict in getattr(tool, 'conflicts', []): @@ -99,10 +99,15 @@ class Frame(object): self.logger.info("Loaded tool drivers:") self.logger.info([tool.name for tool in self.tools]) + deprecated = [tool.name for tool in self.tools if tool.deprecated] + if deprecated: + self.logger.warning("Loaded deprecated tool drivers:") + self.logger.warning(deprecated) + # find entries not handled by any tools self.unhandled = [entry for struct in config - for entry in struct - if entry not in self.handled] + for entry in struct + if entry not in self.handled] if self.unhandled: self.logger.error("The following entries are not handled by any " diff --git a/src/lib/Bcfg2/Client/Tools/RPM.py b/src/lib/Bcfg2/Client/Tools/RPM.py new file mode 100644 index 000000000..1e54dc449 --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/RPM.py @@ -0,0 +1,988 @@ +"""Bcfg2 Support for RPMS""" + +import os.path +import rpm +import rpmtools +import Bcfg2.Client.Tools + +class RPM(Bcfg2.Client.Tools.PkgTool): + """Support for RPM packages.""" + __execs__ = ['/bin/rpm', '/var/lib/rpm'] + __handles__ = [('Package', 'rpm')] + + __req__ = {'Package': ['name', 'version']} + __ireq__ = {'Package': ['url']} + + __new_req__ = {'Package': ['name'], + 'Instance': ['version', 'release', 'arch']} + __new_ireq__ = {'Package': ['uri'], \ + 'Instance': ['simplefile']} + + __gpg_req__ = {'Package': ['name', 'version']} + __gpg_ireq__ = {'Package': ['name', 'version']} + + __new_gpg_req__ = {'Package': ['name'], + 'Instance': ['version', 'release']} + __new_gpg_ireq__ = {'Package': ['name'], + 'Instance': ['version', 'release']} + + conflicts = ['RPMng'] + + pkgtype = 'rpm' + pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"])) + + def __init__(self, logger, setup, config): + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + + # create a global ignore list used when ignoring particular + # files during package verification + self.ignores = [entry.get('name') for struct in config for entry in struct \ + if entry.get('type') == 'ignore'] + self.instance_status = {} + self.extra_instances = [] + self.modlists = {} + self.gpg_keyids = self.getinstalledgpg() + + opt_prefix = self.name.lower() + self.installOnlyPkgs = self.setup["%s_installonly" % opt_prefix] + if 'gpg-pubkey' not in self.installOnlyPkgs: + self.installOnlyPkgs.append('gpg-pubkey') + self.erase_flags = self.setup['%s_erase_flags' % opt_prefix] + self.pkg_checks = self.setup['%s_pkg_checks' % opt_prefix] + self.pkg_verify = self.setup['%s_pkg_verify' % opt_prefix] + self.installed_action = self.setup['%s_installed_action' % opt_prefix] + self.version_fail_action = self.setup['%s_version_fail_action' % + opt_prefix] + self.verify_fail_action = self.setup['%s_verify_fail_action' % + opt_prefix] + self.verify_flags = self.setup['%s_verify_flags' % opt_prefix] + if '' in self.verify_flags: + self.verify_flags.remove('') + + self.logger.debug('%s: installOnlyPackages = %s' % + (self.name, self.installOnlyPkgs)) + self.logger.debug('%s: erase_flags = %s' % + (self.name, self.erase_flags)) + self.logger.debug('%s: pkg_checks = %s' % + (self.name, self.pkg_checks)) + self.logger.debug('%s: pkg_verify = %s' % + (self.name, self.pkg_verify)) + self.logger.debug('%s: installed_action = %s' % + (self.name, self.installed_action)) + self.logger.debug('%s: version_fail_action = %s' % + (self.name, self.version_fail_action)) + self.logger.debug('%s: verify_fail_action = %s' % + (self.name, self.verify_fail_action)) + self.logger.debug('%s: verify_flags = %s' % + (self.name, self.verify_flags)) + + # Force a re- prelink of all packages if prelink exists. + # Many, if not most package verifies can be caused by out of + # date prelinking. + if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']: + cmdrc, output = self.cmd.run('/usr/sbin/prelink -a -mR') + if cmdrc == 0: + self.logger.debug('Pre-emptive prelink succeeded') + else: + # FIXME : this is dumb - what if the output is huge? + self.logger.error('Pre-emptive prelink failed: %s' % output) + + + def RefreshPackages(self): + """ + Creates self.installed{} which is a dict of installed packages. + + The dict items are lists of nevra dicts. This loosely matches the + config from the server and what rpmtools uses to specify pacakges. + + e.g. + + self.installed['foo'] = [ {'name':'foo', 'epoch':None, + 'version':'1', 'release':2, + 'arch':'i386'}, + {'name':'foo', 'epoch':None, + 'version':'1', 'release':2, + 'arch':'x86_64'} ] + """ + self.installed = {} + refresh_ts = rpmtools.rpmtransactionset() + # Don't bother with signature checks at this stage. The GPG keys might + # not be installed. + refresh_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) + for nevra in rpmtools.rpmpackagelist(refresh_ts): + self.installed.setdefault(nevra['name'], []).append(nevra) + if self.setup['debug']: + print("The following package instances are installed:") + for name, instances in list(self.installed.items()): + self.logger.debug(" " + name) + for inst in instances: + self.logger.debug(" %s" %self.str_evra(inst)) + refresh_ts.closeDB() + del refresh_ts + + def VerifyPackage(self, entry, modlist, pinned_version=None): + """ + Verify Package status for entry. + Performs the following: + - Checks for the presence of required Package Instances. + - Compares the evra 'version' info against self.installed{}. + - RPM level package verify (rpm --verify). + - Checks for the presence of unrequired package instances. + + Produces the following dict and list for RPM.Install() to use: + For installs/upgrades/fixes of required instances: + instance_status = { <Instance Element Object>: + { 'installed': True|False, + 'version_fail': True|False, + 'verify_fail': True|False, + 'pkg': <Package Element Object>, + 'modlist': [ <filename>, ... ], + 'verify' : [ <rpm --verify results> ] + }, ...... + } + + For deletions of unrequired instances: + extra_instances = [ <Package Element Object>, ..... ] + + Constructs the text prompts for interactive mode. + """ + instances = [inst for inst in entry if inst.tag == 'Instance' or inst.tag == 'Package'] + if instances == []: + # We have an old style no Instance entry. Convert it to new style. + instance = Bcfg2.Client.XML.SubElement(entry, 'Package') + for attrib in list(entry.attrib.keys()): + instance.attrib[attrib] = entry.attrib[attrib] + if (self.pkg_checks and + entry.get('pkg_checks', 'true').lower() == 'true'): + if 'any' in [entry.get('version'), pinned_version]: + version, release = 'any', 'any' + elif entry.get('version') == 'auto': + if pinned_version != None: + version, release = pinned_version.split('-') + else: + return False + else: + version, release = entry.get('version').split('-') + instance.set('version', version) + instance.set('release', release) + if entry.get('verify', 'true') == 'false': + instance.set('verify', 'false') + instances = [ instance ] + + self.logger.debug("Verifying package instances for %s" % entry.get('name')) + package_fail = False + qtext_versions = '' + + if entry.get('name') in self.installed: + # There is at least one instance installed. + if (self.pkg_checks and + entry.get('pkg_checks', 'true').lower() == 'true'): + rpmTs = rpm.TransactionSet() + rpmHeader = None + for h in rpmTs.dbMatch(rpm.RPMTAG_NAME, entry.get('name')): + if rpmHeader is None or rpm.versionCompare(h, rpmHeader) > 0: + rpmHeader = h + rpmProvides = [ h['provides'] for h in \ + rpmTs.dbMatch(rpm.RPMTAG_NAME, entry.get('name')) ] + rpmIntersection = set(rpmHeader['provides']) & \ + set(self.installOnlyPkgs) + if len(rpmIntersection) > 0: + # Packages that should only be installed or removed. + # e.g. kernels. + self.logger.debug(" Install only package.") + for inst in instances: + self.instance_status.setdefault(inst, {})['installed'] = False + self.instance_status[inst]['version_fail'] = False + if inst.tag == 'Package' and len(self.installed[entry.get('name')]) > 1: + self.logger.error("WARNING: Multiple instances of package %s are installed." % \ + (entry.get('name'))) + for pkg in self.installed[entry.get('name')]: + if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) \ + or self.inst_evra_equal(inst, pkg): + if inst.get('version') == 'any': + self.logger.error("got any version") + self.logger.debug(" %s" % self.str_evra(inst)) + self.instance_status[inst]['installed'] = True + + if (self.pkg_verify and + inst.get('pkg_verify', 'true').lower() == 'true'): + flags = inst.get('verify_flags', '').split(',') + self.verify_flags + if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \ + entry.get('name') != 'gpg-pubkey': + flags += ['nosignature', 'nodigest'] + self.logger.debug('WARNING: Package %s %s requires GPG Public key with ID %s'\ + % (pkg.get('name'), self.str_evra(pkg), \ + pkg.get('gpgkeyid', ''))) + self.logger.debug(' Disabling signature check.') + + if self.setup.get('quick', False): + if rpmtools.prelink_exists: + flags += ['nomd5', 'nosize'] + else: + flags += ['nomd5'] + self.logger.debug(" verify_flags = %s" % flags) + + if inst.get('verify', 'true') == 'false': + self.instance_status[inst]['verify'] = None + else: + vp_ts = rpmtools.rpmtransactionset() + self.instance_status[inst]['verify'] = \ + rpmtools.rpm_verify( vp_ts, pkg, flags) + vp_ts.closeDB() + del vp_ts + + if self.instance_status[inst]['installed'] == False: + self.logger.info(" Package %s %s not installed." % \ + (entry.get('name'), self.str_evra(inst))) + + qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) + entry.set('current_exists', 'false') + else: + # Normal Packages that can be upgraded. + for inst in instances: + self.instance_status.setdefault(inst, {})['installed'] = False + self.instance_status[inst]['version_fail'] = False + + # Only installed packages with the same architecture are + # relevant. + if inst.get('arch', None) == None: + arch_match = self.installed[entry.get('name')] + else: + arch_match = [pkg for pkg in self.installed[entry.get('name')] \ + if pkg.get('arch', None) == inst.get('arch', None)] + + if len(arch_match) > 1: + self.logger.error("Multiple instances of package %s installed with the same achitecture." % \ + (entry.get('name'))) + elif len(arch_match) == 1: + # There is only one installed like there should be. + # Check that it is the right version. + for pkg in arch_match: + if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) or \ + self.inst_evra_equal(inst, pkg): + self.logger.debug(" %s" % self.str_evra(inst)) + self.instance_status[inst]['installed'] = True + + if (self.pkg_verify and + inst.get('pkg_verify', 'true').lower() == 'true'): + flags = inst.get('verify_flags', '').split(',') + self.verify_flags + if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \ + 'nosignature' not in flags: + flags += ['nosignature', 'nodigest'] + self.logger.info('WARNING: Package %s %s requires GPG Public key with ID %s'\ + % (pkg.get('name'), self.str_evra(pkg), \ + pkg.get('gpgkeyid', ''))) + self.logger.info(' Disabling signature check.') + + if self.setup.get('quick', False): + if rpmtools.prelink_exists: + flags += ['nomd5', 'nosize'] + else: + flags += ['nomd5'] + self.logger.debug(" verify_flags = %s" % flags) + + if inst.get('verify', 'true') == 'false': + self.instance_status[inst]['verify'] = None + else: + vp_ts = rpmtools.rpmtransactionset() + self.instance_status[inst]['verify'] = \ + rpmtools.rpm_verify( vp_ts, pkg, flags ) + vp_ts.closeDB() + del vp_ts + + else: + # Wrong version installed. + self.instance_status[inst]['version_fail'] = True + self.logger.info(" Wrong version installed. Want %s, but have %s"\ + % (self.str_evra(inst), self.str_evra(pkg))) + + qtext_versions = qtext_versions + 'U(%s -> %s) ' % \ + (self.str_evra(pkg), self.str_evra(inst)) + elif len(arch_match) == 0: + # This instance is not installed. + self.instance_status[inst]['installed'] = False + self.logger.info(" %s is not installed." % self.str_evra(inst)) + qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) + + # Check the rpm verify results. + for inst in instances: + instance_fail = False + # Dump the rpm verify results. + #****Write something to format this nicely.***** + if self.setup['debug'] and self.instance_status[inst].get('verify', None): + self.logger.debug(self.instance_status[inst]['verify']) + + self.instance_status[inst]['verify_fail'] = False + if self.instance_status[inst].get('verify', None): + if len(self.instance_status[inst].get('verify')) > 1: + self.logger.info("WARNING: Verification of more than one package instance.") + + for result in self.instance_status[inst]['verify']: + + # Check header results + if result.get('hdr', None): + instance_fail = True + self.instance_status[inst]['verify_fail'] = True + + # Check dependency results + if result.get('deps', None): + instance_fail = True + self.instance_status[inst]['verify_fail'] = True + + # Check the rpm verify file results against the modlist + # and entry and per Instance Ignores. + ignores = [ig.get('name') for ig in entry.findall('Ignore')] + \ + [ig.get('name') for ig in inst.findall('Ignore')] + \ + self.ignores + for file_result in result.get('files', []): + if file_result[-1] not in modlist + ignores: + instance_fail = True + self.instance_status[inst]['verify_fail'] = True + else: + self.logger.debug(" Modlist/Ignore match: %s" % \ + (file_result[-1])) + + if instance_fail == True: + self.logger.debug("*** Instance %s failed RPM verification ***" % \ + self.str_evra(inst)) + qtext_versions = qtext_versions + 'R(%s) ' % self.str_evra(inst) + self.modlists[entry] = modlist + + # Attach status structure for return to server for reporting. + inst.set('verify_status', str(self.instance_status[inst])) + + if self.instance_status[inst]['installed'] == False or \ + self.instance_status[inst].get('version_fail', False)== True or \ + self.instance_status[inst].get('verify_fail', False) == True: + package_fail = True + self.instance_status[inst]['pkg'] = entry + self.modlists[entry] = modlist + + # Find Installed Instances that are not in the Config. + extra_installed = self.FindExtraInstances(entry, self.installed[entry.get('name')]) + if extra_installed != None: + package_fail = True + self.extra_instances.append(extra_installed) + for inst in extra_installed.findall('Instance'): + qtext_versions = qtext_versions + 'D(%s) ' % self.str_evra(inst) + self.logger.debug("Found Extra Instances %s" % qtext_versions) + + if package_fail == True: + self.logger.info(" Package %s failed verification." % \ + (entry.get('name'))) + qtext = 'Install/Upgrade/delete Package %s instance(s) - %s (y/N) ' % \ + (entry.get('name'), qtext_versions) + entry.set('qtext', qtext) + + bcfg2_versions = '' + for bcfg2_inst in [inst for inst in instances if inst.tag == 'Instance']: + bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(bcfg2_inst) + if bcfg2_versions != '': + entry.set('version', bcfg2_versions) + installed_versions = '' + + for installed_inst in self.installed[entry.get('name')]: + installed_versions = installed_versions + '(%s) ' % \ + self.str_evra(installed_inst) + + entry.set('current_version', installed_versions) + return False + + else: + # There are no Instances of this package installed. + self.logger.debug("Package %s has no instances installed" % (entry.get('name'))) + entry.set('current_exists', 'false') + bcfg2_versions = '' + for inst in instances: + qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) + self.instance_status.setdefault(inst, {})['installed'] = False + self.modlists[entry] = modlist + self.instance_status[inst]['pkg'] = entry + if inst.tag == 'Instance': + bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(inst) + if bcfg2_versions != '': + entry.set('version', bcfg2_versions) + entry.set('qtext', "Install Package %s Instance(s) %s? (y/N) " % \ + (entry.get('name'), qtext_versions)) + + return False + return True + + def RemovePackages(self, packages): + """ + Remove specified entries. + + packages is a list of Package Entries with Instances generated + by FindExtraPackages(). + + """ + self.logger.debug('Running RPM.RemovePackages()') + + pkgspec_list = [] + for pkg in packages: + for inst in pkg: + if pkg.get('name') != 'gpg-pubkey': + pkgspec = { 'name':pkg.get('name'), + 'epoch':inst.get('epoch', None), + 'version':inst.get('version'), + 'release':inst.get('release'), + 'arch':inst.get('arch') } + pkgspec_list.append(pkgspec) + else: + pkgspec = { 'name':pkg.get('name'), + 'version':inst.get('version'), + 'release':inst.get('release')} + self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ + % (pkgspec.get('name'), self.str_evra(pkgspec))) + self.logger.info(" This package will be deleted in a future version of the RPM driver.") + #pkgspec_list.append(pkg_spec) + + erase_results = rpmtools.rpm_erase(pkgspec_list, self.erase_flags) + if erase_results == []: + self.modified += packages + for pkg in pkgspec_list: + self.logger.info("Deleted %s %s" % (pkg.get('name'), self.str_evra(pkg))) + else: + self.logger.info("Bulk erase failed with errors:") + self.logger.debug("Erase results = %s" % erase_results) + self.logger.info("Attempting individual erase for each package.") + pkgspec_list = [] + for pkg in packages: + pkg_modified = False + for inst in pkg: + if pkg.get('name') != 'gpg-pubkey': + pkgspec = { 'name':pkg.get('name'), + 'epoch':inst.get('epoch', None), + 'version':inst.get('version'), + 'release':inst.get('release'), + 'arch':inst.get('arch') } + pkgspec_list.append(pkgspec) + else: + pkgspec = { 'name':pkg.get('name'), + 'version':inst.get('version'), + 'release':inst.get('release')} + self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ + % (pkgspec.get('name'), self.str_evra(pkgspec))) + self.logger.info(" This package will be deleted in a future version of the RPM driver.") + continue # Don't delete the gpg-pubkey packages for now. + erase_results = rpmtools.rpm_erase([pkgspec], self.erase_flags) + if erase_results == []: + pkg_modified = True + self.logger.info("Deleted %s %s" % \ + (pkgspec.get('name'), self.str_evra(pkgspec))) + else: + self.logger.error("unable to delete %s %s" % \ + (pkgspec.get('name'), self.str_evra(pkgspec))) + self.logger.debug("Failure = %s" % erase_results) + if pkg_modified == True: + self.modified.append(pkg) + + self.RefreshPackages() + self.extra = self.FindExtraPackages() + + def FixInstance(self, instance, inst_status): + """ + Control if a reinstall of a package happens or not based on the + results from RPM.VerifyPackage(). + + Return True to reinstall, False to not reintstall. + + """ + fix = False + + if inst_status.get('installed', False) == False: + if instance.get('installed_action', 'install') == "install" and \ + self.installed_action == "install": + fix = True + else: + self.logger.debug('Installed Action for %s %s is to not install' % \ + (inst_status.get('pkg').get('name'), + self.str_evra(instance))) + + elif inst_status.get('version_fail', False) == True: + if instance.get('version_fail_action', 'upgrade') == "upgrade" and \ + self.version_fail_action == "upgrade": + fix = True + else: + self.logger.debug('Version Fail Action for %s %s is to not upgrade' % \ + (inst_status.get('pkg').get('name'), + self.str_evra(instance))) + + elif inst_status.get('verify_fail', False) == True and self.name == "RPM": + # yum can't reinstall packages so only do this for rpm. + if instance.get('verify_fail_action', 'reinstall') == "reinstall" and \ + self.verify_fail_action == "reinstall": + for inst in inst_status.get('verify'): + # This needs to be a for loop rather than a straight get() + # because the underlying routines handle multiple packages + # and return a list of results. + self.logger.debug('reinstall_check: %s %s:%s-%s.%s' % inst.get('nevra')) + + if inst.get("hdr", False): + fix = True + + elif inst.get('files', False): + # Parse rpm verify file results + for file_result in inst.get('files', []): + self.logger.debug('reinstall_check: file: %s' % file_result) + if file_result[-2] != 'c': + fix = True + break + + # Shouldn't really need this, but included for clarity. + elif inst.get("deps", False): + fix = False + else: + self.logger.debug('Verify Fail Action for %s %s is to not reinstall' % \ + (inst_status.get('pkg').get('name'), + self.str_evra(instance))) + + return fix + + def Install(self, packages, states): + """ + Try and fix everything that RPM.VerifyPackages() found wrong for + each Package Entry. This can result in individual RPMs being + installed (for the first time), reinstalled, deleted, downgraded + or upgraded. + + packages is a list of Package Elements that has + states[<Package Element>] == False + + The following effects occur: + - states{} is conditionally updated for each package. + - self.installed{} is rebuilt, possibly multiple times. + - self.instance_statusi{} is conditionally updated for each instance + of a package. + - Each package will be added to self.modified[] if its states{} + entry is set to True. + + """ + self.logger.info('Runing RPM.Install()') + + install_only_pkgs = [] + gpg_keys = [] + upgrade_pkgs = [] + + # Remove extra instances. + # Can not reverify because we don't have a package entry. + if len(self.extra_instances) > 0: + if (self.setup.get('remove') == 'all' or \ + self.setup.get('remove') == 'packages') and\ + not self.setup.get('dryrun'): + self.RemovePackages(self.extra_instances) + else: + self.logger.info("The following extra package instances will be removed by the '-r' option:") + for pkg in self.extra_instances: + for inst in pkg: + self.logger.info(" %s %s" % (pkg.get('name'), self.str_evra(inst))) + + # Figure out which instances of the packages actually need something + # doing to them and place in the appropriate work 'queue'. + for pkg in packages: + for inst in [instn for instn in pkg if instn.tag \ + in ['Instance', 'Package']]: + if self.FixInstance(inst, self.instance_status[inst]): + if pkg.get('name') == 'gpg-pubkey': + gpg_keys.append(inst) + elif pkg.get('name') in self.installOnlyPkgs: + install_only_pkgs.append(inst) + else: + upgrade_pkgs.append(inst) + + # Fix installOnlyPackages + if len(install_only_pkgs) > 0: + self.logger.info("Attempting to install 'install only packages'") + install_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) \ + for inst in install_only_pkgs]) + self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) + cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ + install_args) + if cmdrc == 0: + # The rpm command succeeded. All packages installed. + self.logger.info("Single Pass for InstallOnlyPkgs Succeded") + self.RefreshPackages() + + else: + # The rpm command failed. No packages installed. + # Try installing instances individually. + self.logger.error("Single Pass for InstallOnlyPackages Failed") + installed_instances = [] + for inst in install_only_pkgs: + install_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) + self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) + cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ + install_args) + if cmdrc == 0: + installed_instances.append(inst) + else: + self.logger.debug("InstallOnlyPackage %s %s would not install." % \ + (self.instance_status[inst].get('pkg').get('name'), \ + self.str_evra(inst))) + + install_pkg_set = set([self.instance_status[inst].get('pkg') \ + for inst in install_only_pkgs]) + self.RefreshPackages() + + # Install GPG keys. + if len(gpg_keys) > 0: + for inst in gpg_keys: + self.logger.info("Installing GPG keys.") + key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) + cmdrc, output = self.cmd.run("rpm --import %s" % key_arg) + if cmdrc != 0: + self.logger.debug("Unable to install %s-%s" % \ + (self.instance_status[inst].get('pkg').get('name'), \ + self.str_evra(inst))) + else: + self.logger.debug("Installed %s-%s-%s" % \ + (self.instance_status[inst].get('pkg').get('name'), \ + inst.get('version'), inst.get('release'))) + self.RefreshPackages() + self.gpg_keyids = self.getinstalledgpg() + pkg = self.instance_status[gpg_keys[0]].get('pkg') + states[pkg] = self.VerifyPackage(pkg, []) + + # Fix upgradeable packages. + if len(upgrade_pkgs) > 0: + self.logger.info("Attempting to upgrade packages") + upgrade_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) \ + for inst in upgrade_pkgs]) + cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ + upgrade_args) + if cmdrc == 0: + # The rpm command succeeded. All packages upgraded. + self.logger.info("Single Pass for Upgraded Packages Succeded") + upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ + for inst in upgrade_pkgs]) + self.RefreshPackages() + else: + # The rpm command failed. No packages upgraded. + # Try upgrading instances individually. + self.logger.error("Single Pass for Upgrading Packages Failed") + upgraded_instances = [] + for inst in upgrade_pkgs: + upgrade_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) + #self.logger.debug("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ + # upgrade_args) + cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % upgrade_args) + if cmdrc == 0: + upgraded_instances.append(inst) + else: + self.logger.debug("Package %s %s would not upgrade." % \ + (self.instance_status[inst].get('pkg').get('name'), \ + self.str_evra(inst))) + + upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ + for inst in upgrade_pkgs]) + self.RefreshPackages() + + if not self.setup['kevlar']: + for pkg_entry in packages: + self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name'))) + states[pkg_entry] = self.VerifyPackage(pkg_entry, \ + self.modlists.get(pkg_entry, [])) + + for entry in [ent for ent in packages if states[ent]]: + self.modified.append(entry) + + def canInstall(self, entry): + """Test if entry has enough information to be installed.""" + if not self.handlesEntry(entry): + return False + + if 'failure' in entry.attrib: + self.logger.error("Cannot install entry %s:%s with bind failure" % \ + (entry.tag, entry.get('name'))) + return False + + + instances = entry.findall('Instance') + + # If the entry wasn't verifiable, then we really don't want to try and fix something + # that we don't know is broken. + if not self.canVerify(entry): + self.logger.debug("WARNING: Package %s was not verifiable, not passing to Install()" \ + % entry.get('name')) + return False + + if not instances: + # Old non Instance format, unmodified. + if entry.get('name') == 'gpg-pubkey': + # gpg-pubkey packages aren't really pacakges, so we have to do + # something a little different. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__gpg_ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + return False + else: + if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + return False + else: + if entry.get('name') == 'gpg-pubkey': + # gpg-pubkey packages aren't really pacakges, so we have to do + # something a little different. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__new_gpg_ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + return False + # Check that the Instance Level has what we need for verification. + for inst in instances: + if [attr for attr in self.__new_gpg_ireq__[inst.tag] \ + if attr not in inst.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install"\ + % (inst.tag, entry.get('name'))) + return False + else: + # New format with Instances. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__new_ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + self.logger.error(" Required attributes that may not be present are %s" \ + % (self.__new_ireq__[entry.tag])) + return False + # Check that the Instance Level has what we need for verification. + for inst in instances: + if inst.tag == 'Instance': + if [attr for attr in self.__new_ireq__[inst.tag] \ + if attr not in inst.attrib]: + self.logger.error("Incomplete information for %s of package %s; cannot install" \ + % (inst.tag, entry.get('name'))) + self.logger.error(" Required attributes that may not be present are %s" \ + % (self.__new_ireq__[inst.tag])) + return False + return True + + def canVerify(self, entry): + """ + Test if entry has enough information to be verified. + + Three types of entries are checked. + Old style Package + New style Package with Instances + pgp-pubkey packages + + Also the old style entries get modified after the first + VerifyPackage() run, so there needs to be a second test. + + """ + if not self.handlesEntry(entry): + return False + + if 'failure' in entry.attrib: + self.logger.error("Entry %s:%s reports bind failure: %s" % \ + (entry.tag, entry.get('name'), entry.get('failure'))) + return False + + # We don't want to do any checks so we don't care what the entry has in it. + if (not self.pkg_checks or + entry.get('pkg_checks', 'true').lower() == 'false'): + return True + + instances = entry.findall('Instance') + + if not instances: + # Old non Instance format, unmodified. + if entry.get('name') == 'gpg-pubkey': + # gpg-pubkey packages aren't really pacakges, so we have to do + # something a little different. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__gpg_req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + elif entry.tag == 'Path' and entry.get('type') == 'ignore': + # ignored Paths are only relevant during failed package + # verification + pass + else: + if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + else: + if entry.get('name') == 'gpg-pubkey': + # gpg-pubkey packages aren't really pacakges, so we have to do + # something a little different. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__new_gpg_req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + # Check that the Instance Level has what we need for verification. + for inst in instances: + if [attr for attr in self.__new_gpg_req__[inst.tag] \ + if attr not in inst.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (inst.tag, inst.get('name'))) + return False + else: + # New format with Instances, or old style modified. + # Check that the Package Level has what we need for verification. + if [attr for attr in self.__new_req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + # Check that the Instance Level has what we need for verification. + for inst in instances: + if inst.tag == 'Instance': + if [attr for attr in self.__new_req__[inst.tag] \ + if attr not in inst.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (inst.tag, inst.get('name'))) + return False + return True + + def FindExtraPackages(self): + """Find extra packages.""" + packages = [entry.get('name') for entry in self.getSupportedEntries()] + extras = [] + + for (name, instances) in list(self.installed.items()): + if name not in packages: + extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) + for installed_inst in instances: + if self.setup['extra']: + self.logger.info("Extra Package %s %s." % \ + (name, self.str_evra(installed_inst))) + tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ + version = installed_inst.get('version'), \ + release = installed_inst.get('release')) + if installed_inst.get('epoch', None) != None: + tmp_entry.set('epoch', str(installed_inst.get('epoch'))) + if installed_inst.get('arch', None) != None: + tmp_entry.set('arch', installed_inst.get('arch')) + extras.append(extra_entry) + return extras + + + def FindExtraInstances(self, pkg_entry, installed_entry): + """ + Check for installed instances that are not in the config. + Return a Package Entry with Instances to remove, or None if there + are no Instances to remove. + + """ + name = pkg_entry.get('name') + extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) + instances = [inst for inst in pkg_entry if inst.tag == 'Instance' or inst.tag == 'Package'] + if name in self.installOnlyPkgs: + for installed_inst in installed_entry: + not_found = True + for inst in instances: + if self.pkg_vr_equal(inst, installed_inst) or \ + self.inst_evra_equal(inst, installed_inst): + not_found = False + break + if not_found == True: + # Extra package. + self.logger.info("Extra InstallOnlyPackage %s %s." % \ + (name, self.str_evra(installed_inst))) + tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ + version = installed_inst.get('version'), \ + release = installed_inst.get('release')) + if installed_inst.get('epoch', None) != None: + tmp_entry.set('epoch', str(installed_inst.get('epoch'))) + if installed_inst.get('arch', None) != None: + tmp_entry.set('arch', installed_inst.get('arch')) + else: + # Normal package, only check arch. + for installed_inst in installed_entry: + not_found = True + for inst in instances: + if installed_inst.get('arch', None) == inst.get('arch', None) or\ + inst.tag == 'Package': + not_found = False + break + if not_found: + self.logger.info("Extra Normal Package Instance %s %s" % \ + (name, self.str_evra(installed_inst))) + tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ + version = installed_inst.get('version'), \ + release = installed_inst.get('release')) + if installed_inst.get('epoch', None) != None: + tmp_entry.set('epoch', str(installed_inst.get('epoch'))) + if installed_inst.get('arch', None) != None: + tmp_entry.set('arch', installed_inst.get('arch')) + + if len(extra_entry) == 0: + extra_entry = None + + return extra_entry + + def str_evra(self, instance): + """Convert evra dict entries to a string.""" + if instance.get('epoch', '*') in ['*', None]: + return '%s-%s.%s' % (instance.get('version', '*'), + instance.get('release', '*'), + instance.get('arch', '*')) + else: + return '%s:%s-%s.%s' % (instance.get('epoch', '*'), + instance.get('version', '*'), + instance.get('release', '*'), + instance.get('arch', '*')) + + def pkg_vr_equal(self, config_entry, installed_entry): + ''' + Compare old style entry to installed entry. Which means ignore + the epoch and arch. + ''' + if (config_entry.tag == 'Package' and \ + config_entry.get('version') == installed_entry.get('version') and \ + config_entry.get('release') == installed_entry.get('release')): + return True + else: + return False + + def inst_evra_equal(self, config_entry, installed_entry): + """Compare new style instance to installed entry.""" + + if config_entry.get('epoch', None) != None: + epoch = int(config_entry.get('epoch')) + else: + epoch = None + + if (config_entry.tag == 'Instance' and \ + (epoch == installed_entry.get('epoch', 0) or \ + (epoch == 0 and installed_entry.get('epoch', 0) == None) or \ + (epoch == None and installed_entry.get('epoch', 0) == 0)) and \ + config_entry.get('version') == installed_entry.get('version') and \ + config_entry.get('release') == installed_entry.get('release') and \ + config_entry.get('arch', None) == installed_entry.get('arch', None)): + return True + else: + return False + + def getinstalledgpg(self): + """ + Create a list of installed GPG key IDs. + + The pgp-pubkey package version is the least significant 4 bytes + (big-endian) of the key ID which is good enough for our purposes. + + """ + init_ts = rpmtools.rpmtransactionset() + init_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) + gpg_hdrs = rpmtools.getheadersbykeyword(init_ts, **{'name':'gpg-pubkey'}) + keyids = [ header[rpm.RPMTAG_VERSION] for header in gpg_hdrs] + keyids.append('None') + init_ts.closeDB() + del init_ts + return keyids + + def VerifyPath(self, entry, _): + """ + We don't do anything here since all + Paths are processed in __init__ + """ + return True diff --git a/src/lib/Bcfg2/Client/Tools/RPMng.py b/src/lib/Bcfg2/Client/Tools/RPMng.py index 91e2180ae..f5a234f8d 100644 --- a/src/lib/Bcfg2/Client/Tools/RPMng.py +++ b/src/lib/Bcfg2/Client/Tools/RPMng.py @@ -1,987 +1,8 @@ -"""Bcfg2 Support for RPMS""" +""" RPM driver called 'RPMng' for backwards compat """ -import os.path -import rpm -import rpmtools -import Bcfg2.Client.Tools +from Bcfg2.Client.Tools.RPM import RPM -class RPMng(Bcfg2.Client.Tools.PkgTool): - """Support for RPM packages.""" - name = 'RPMng' - __execs__ = ['/bin/rpm', '/var/lib/rpm'] - __handles__ = [('Package', 'rpm')] - - __req__ = {'Package': ['name', 'version']} - __ireq__ = {'Package': ['url']} - - __new_req__ = {'Package': ['name'], 'Instance': ['version', 'release', 'arch']} - __new_ireq__ = {'Package': ['uri'], \ - 'Instance': ['simplefile']} - - __gpg_req__ = {'Package': ['name', 'version']} - __gpg_ireq__ = {'Package': ['name', 'version']} - - __new_gpg_req__ = {'Package': ['name'], 'Instance': ['version', 'release']} - __new_gpg_ireq__ = {'Package': ['name'], 'Instance': ['version', 'release']} - - conflicts = ['RPM'] - - pkgtype = 'rpm' - pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"])) - - def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - - # create a global ignore list used when ignoring particular - # files during package verification - self.ignores = [entry.get('name') for struct in config for entry in struct \ - if entry.get('type') == 'ignore'] - self.instance_status = {} - self.extra_instances = [] - self.modlists = {} - self.gpg_keyids = self.getinstalledgpg() - - opt_prefix = self.name.lower() - self.installOnlyPkgs = self.setup["%s_installonly" % opt_prefix] - if 'gpg-pubkey' not in self.installOnlyPkgs: - self.installOnlyPkgs.append('gpg-pubkey') - self.erase_flags = self.setup['%s_erase_flags' % opt_prefix] - self.pkg_checks = self.setup['%s_pkg_checks' % opt_prefix] - self.pkg_verify = self.setup['%s_pkg_verify' % opt_prefix] - self.installed_action = self.setup['%s_installed_action' % opt_prefix] - self.version_fail_action = self.setup['%s_version_fail_action' % - opt_prefix] - self.verify_fail_action = self.setup['%s_verify_fail_action' % - opt_prefix] - self.verify_flags = self.setup['%s_verify_flags' % opt_prefix] - if '' in self.verify_flags: - self.verify_flags.remove('') - - self.logger.debug('%s: installOnlyPackages = %s' % - (self.name, self.installOnlyPkgs)) - self.logger.debug('%s: erase_flags = %s' % - (self.name, self.erase_flags)) - self.logger.debug('%s: pkg_checks = %s' % - (self.name, self.pkg_checks)) - self.logger.debug('%s: pkg_verify = %s' % - (self.name, self.pkg_verify)) - self.logger.debug('%s: installed_action = %s' % - (self.name, self.installed_action)) - self.logger.debug('%s: version_fail_action = %s' % - (self.name, self.version_fail_action)) - self.logger.debug('%s: verify_fail_action = %s' % - (self.name, self.verify_fail_action)) - self.logger.debug('%s: verify_flags = %s' % - (self.name, self.verify_flags)) - - # Force a re- prelink of all packages if prelink exists. - # Many, if not most package verifies can be caused by out of - # date prelinking. - if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']: - cmdrc, output = self.cmd.run('/usr/sbin/prelink -a -mR') - if cmdrc == 0: - self.logger.debug('Pre-emptive prelink succeeded') - else: - # FIXME : this is dumb - what if the output is huge? - self.logger.error('Pre-emptive prelink failed: %s' % output) - - - def RefreshPackages(self): - """ - Creates self.installed{} which is a dict of installed packages. - - The dict items are lists of nevra dicts. This loosely matches the - config from the server and what rpmtools uses to specify pacakges. - - e.g. - - self.installed['foo'] = [ {'name':'foo', 'epoch':None, - 'version':'1', 'release':2, - 'arch':'i386'}, - {'name':'foo', 'epoch':None, - 'version':'1', 'release':2, - 'arch':'x86_64'} ] - """ - self.installed = {} - refresh_ts = rpmtools.rpmtransactionset() - # Don't bother with signature checks at this stage. The GPG keys might - # not be installed. - refresh_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) - for nevra in rpmtools.rpmpackagelist(refresh_ts): - self.installed.setdefault(nevra['name'], []).append(nevra) - if self.setup['debug']: - print("The following package instances are installed:") - for name, instances in list(self.installed.items()): - self.logger.debug(" " + name) - for inst in instances: - self.logger.debug(" %s" %self.str_evra(inst)) - refresh_ts.closeDB() - del refresh_ts - - def VerifyPackage(self, entry, modlist, pinned_version=None): - """ - Verify Package status for entry. - Performs the following: - - Checks for the presence of required Package Instances. - - Compares the evra 'version' info against self.installed{}. - - RPM level package verify (rpm --verify). - - Checks for the presence of unrequired package instances. - - Produces the following dict and list for RPMng.Install() to use: - For installs/upgrades/fixes of required instances: - instance_status = { <Instance Element Object>: - { 'installed': True|False, - 'version_fail': True|False, - 'verify_fail': True|False, - 'pkg': <Package Element Object>, - 'modlist': [ <filename>, ... ], - 'verify' : [ <rpm --verify results> ] - }, ...... - } - - For deletions of unrequired instances: - extra_instances = [ <Package Element Object>, ..... ] - - Constructs the text prompts for interactive mode. - """ - instances = [inst for inst in entry if inst.tag == 'Instance' or inst.tag == 'Package'] - if instances == []: - # We have an old style no Instance entry. Convert it to new style. - instance = Bcfg2.Client.XML.SubElement(entry, 'Package') - for attrib in list(entry.attrib.keys()): - instance.attrib[attrib] = entry.attrib[attrib] - if (self.pkg_checks and - entry.get('pkg_checks', 'true').lower() == 'true'): - if 'any' in [entry.get('version'), pinned_version]: - version, release = 'any', 'any' - elif entry.get('version') == 'auto': - if pinned_version != None: - version, release = pinned_version.split('-') - else: - return False - else: - version, release = entry.get('version').split('-') - instance.set('version', version) - instance.set('release', release) - if entry.get('verify', 'true') == 'false': - instance.set('verify', 'false') - instances = [ instance ] - - self.logger.debug("Verifying package instances for %s" % entry.get('name')) - package_fail = False - qtext_versions = '' - - if entry.get('name') in self.installed: - # There is at least one instance installed. - if (self.pkg_checks and - entry.get('pkg_checks', 'true').lower() == 'true'): - rpmTs = rpm.TransactionSet() - rpmHeader = None - for h in rpmTs.dbMatch(rpm.RPMTAG_NAME, entry.get('name')): - if rpmHeader is None or rpm.versionCompare(h, rpmHeader) > 0: - rpmHeader = h - rpmProvides = [ h['provides'] for h in \ - rpmTs.dbMatch(rpm.RPMTAG_NAME, entry.get('name')) ] - rpmIntersection = set(rpmHeader['provides']) & \ - set(self.installOnlyPkgs) - if len(rpmIntersection) > 0: - # Packages that should only be installed or removed. - # e.g. kernels. - self.logger.debug(" Install only package.") - for inst in instances: - self.instance_status.setdefault(inst, {})['installed'] = False - self.instance_status[inst]['version_fail'] = False - if inst.tag == 'Package' and len(self.installed[entry.get('name')]) > 1: - self.logger.error("WARNING: Multiple instances of package %s are installed." % \ - (entry.get('name'))) - for pkg in self.installed[entry.get('name')]: - if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) \ - or self.inst_evra_equal(inst, pkg): - if inst.get('version') == 'any': - self.logger.error("got any version") - self.logger.debug(" %s" % self.str_evra(inst)) - self.instance_status[inst]['installed'] = True - - if (self.pkg_verify and - inst.get('pkg_verify', 'true').lower() == 'true'): - flags = inst.get('verify_flags', '').split(',') + self.verify_flags - if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \ - entry.get('name') != 'gpg-pubkey': - flags += ['nosignature', 'nodigest'] - self.logger.debug('WARNING: Package %s %s requires GPG Public key with ID %s'\ - % (pkg.get('name'), self.str_evra(pkg), \ - pkg.get('gpgkeyid', ''))) - self.logger.debug(' Disabling signature check.') - - if self.setup.get('quick', False): - if rpmtools.prelink_exists: - flags += ['nomd5', 'nosize'] - else: - flags += ['nomd5'] - self.logger.debug(" verify_flags = %s" % flags) - - if inst.get('verify', 'true') == 'false': - self.instance_status[inst]['verify'] = None - else: - vp_ts = rpmtools.rpmtransactionset() - self.instance_status[inst]['verify'] = \ - rpmtools.rpm_verify( vp_ts, pkg, flags) - vp_ts.closeDB() - del vp_ts - - if self.instance_status[inst]['installed'] == False: - self.logger.info(" Package %s %s not installed." % \ - (entry.get('name'), self.str_evra(inst))) - - qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) - entry.set('current_exists', 'false') - else: - # Normal Packages that can be upgraded. - for inst in instances: - self.instance_status.setdefault(inst, {})['installed'] = False - self.instance_status[inst]['version_fail'] = False - - # Only installed packages with the same architecture are - # relevant. - if inst.get('arch', None) == None: - arch_match = self.installed[entry.get('name')] - else: - arch_match = [pkg for pkg in self.installed[entry.get('name')] \ - if pkg.get('arch', None) == inst.get('arch', None)] - - if len(arch_match) > 1: - self.logger.error("Multiple instances of package %s installed with the same achitecture." % \ - (entry.get('name'))) - elif len(arch_match) == 1: - # There is only one installed like there should be. - # Check that it is the right version. - for pkg in arch_match: - if inst.get('version') == 'any' or self.pkg_vr_equal(inst, pkg) or \ - self.inst_evra_equal(inst, pkg): - self.logger.debug(" %s" % self.str_evra(inst)) - self.instance_status[inst]['installed'] = True - - if (self.pkg_verify and - inst.get('pkg_verify', 'true').lower() == 'true'): - flags = inst.get('verify_flags', '').split(',') + self.verify_flags - if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \ - 'nosignature' not in flags: - flags += ['nosignature', 'nodigest'] - self.logger.info('WARNING: Package %s %s requires GPG Public key with ID %s'\ - % (pkg.get('name'), self.str_evra(pkg), \ - pkg.get('gpgkeyid', ''))) - self.logger.info(' Disabling signature check.') - - if self.setup.get('quick', False): - if rpmtools.prelink_exists: - flags += ['nomd5', 'nosize'] - else: - flags += ['nomd5'] - self.logger.debug(" verify_flags = %s" % flags) - - if inst.get('verify', 'true') == 'false': - self.instance_status[inst]['verify'] = None - else: - vp_ts = rpmtools.rpmtransactionset() - self.instance_status[inst]['verify'] = \ - rpmtools.rpm_verify( vp_ts, pkg, flags ) - vp_ts.closeDB() - del vp_ts - - else: - # Wrong version installed. - self.instance_status[inst]['version_fail'] = True - self.logger.info(" Wrong version installed. Want %s, but have %s"\ - % (self.str_evra(inst), self.str_evra(pkg))) - - qtext_versions = qtext_versions + 'U(%s -> %s) ' % \ - (self.str_evra(pkg), self.str_evra(inst)) - elif len(arch_match) == 0: - # This instance is not installed. - self.instance_status[inst]['installed'] = False - self.logger.info(" %s is not installed." % self.str_evra(inst)) - qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) - - # Check the rpm verify results. - for inst in instances: - instance_fail = False - # Dump the rpm verify results. - #****Write something to format this nicely.***** - if self.setup['debug'] and self.instance_status[inst].get('verify', None): - self.logger.debug(self.instance_status[inst]['verify']) - - self.instance_status[inst]['verify_fail'] = False - if self.instance_status[inst].get('verify', None): - if len(self.instance_status[inst].get('verify')) > 1: - self.logger.info("WARNING: Verification of more than one package instance.") - - for result in self.instance_status[inst]['verify']: - - # Check header results - if result.get('hdr', None): - instance_fail = True - self.instance_status[inst]['verify_fail'] = True - - # Check dependency results - if result.get('deps', None): - instance_fail = True - self.instance_status[inst]['verify_fail'] = True - - # Check the rpm verify file results against the modlist - # and entry and per Instance Ignores. - ignores = [ig.get('name') for ig in entry.findall('Ignore')] + \ - [ig.get('name') for ig in inst.findall('Ignore')] + \ - self.ignores - for file_result in result.get('files', []): - if file_result[-1] not in modlist + ignores: - instance_fail = True - self.instance_status[inst]['verify_fail'] = True - else: - self.logger.debug(" Modlist/Ignore match: %s" % \ - (file_result[-1])) - - if instance_fail == True: - self.logger.debug("*** Instance %s failed RPM verification ***" % \ - self.str_evra(inst)) - qtext_versions = qtext_versions + 'R(%s) ' % self.str_evra(inst) - self.modlists[entry] = modlist - - # Attach status structure for return to server for reporting. - inst.set('verify_status', str(self.instance_status[inst])) - - if self.instance_status[inst]['installed'] == False or \ - self.instance_status[inst].get('version_fail', False)== True or \ - self.instance_status[inst].get('verify_fail', False) == True: - package_fail = True - self.instance_status[inst]['pkg'] = entry - self.modlists[entry] = modlist - - # Find Installed Instances that are not in the Config. - extra_installed = self.FindExtraInstances(entry, self.installed[entry.get('name')]) - if extra_installed != None: - package_fail = True - self.extra_instances.append(extra_installed) - for inst in extra_installed.findall('Instance'): - qtext_versions = qtext_versions + 'D(%s) ' % self.str_evra(inst) - self.logger.debug("Found Extra Instances %s" % qtext_versions) - - if package_fail == True: - self.logger.info(" Package %s failed verification." % \ - (entry.get('name'))) - qtext = 'Install/Upgrade/delete Package %s instance(s) - %s (y/N) ' % \ - (entry.get('name'), qtext_versions) - entry.set('qtext', qtext) - - bcfg2_versions = '' - for bcfg2_inst in [inst for inst in instances if inst.tag == 'Instance']: - bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(bcfg2_inst) - if bcfg2_versions != '': - entry.set('version', bcfg2_versions) - installed_versions = '' - - for installed_inst in self.installed[entry.get('name')]: - installed_versions = installed_versions + '(%s) ' % \ - self.str_evra(installed_inst) - - entry.set('current_version', installed_versions) - return False - - else: - # There are no Instances of this package installed. - self.logger.debug("Package %s has no instances installed" % (entry.get('name'))) - entry.set('current_exists', 'false') - bcfg2_versions = '' - for inst in instances: - qtext_versions = qtext_versions + 'I(%s) ' % self.str_evra(inst) - self.instance_status.setdefault(inst, {})['installed'] = False - self.modlists[entry] = modlist - self.instance_status[inst]['pkg'] = entry - if inst.tag == 'Instance': - bcfg2_versions = bcfg2_versions + '(%s) ' % self.str_evra(inst) - if bcfg2_versions != '': - entry.set('version', bcfg2_versions) - entry.set('qtext', "Install Package %s Instance(s) %s? (y/N) " % \ - (entry.get('name'), qtext_versions)) - - return False - return True - - def RemovePackages(self, packages): - """ - Remove specified entries. - - packages is a list of Package Entries with Instances generated - by FindExtraPackages(). - - """ - self.logger.debug('Running RPMng.RemovePackages()') - - pkgspec_list = [] - for pkg in packages: - for inst in pkg: - if pkg.get('name') != 'gpg-pubkey': - pkgspec = { 'name':pkg.get('name'), - 'epoch':inst.get('epoch', None), - 'version':inst.get('version'), - 'release':inst.get('release'), - 'arch':inst.get('arch') } - pkgspec_list.append(pkgspec) - else: - pkgspec = { 'name':pkg.get('name'), - 'version':inst.get('version'), - 'release':inst.get('release')} - self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ - % (pkgspec.get('name'), self.str_evra(pkgspec))) - self.logger.info(" This package will be deleted in a future version of the RPMng driver.") - #pkgspec_list.append(pkg_spec) - - erase_results = rpmtools.rpm_erase(pkgspec_list, self.erase_flags) - if erase_results == []: - self.modified += packages - for pkg in pkgspec_list: - self.logger.info("Deleted %s %s" % (pkg.get('name'), self.str_evra(pkg))) - else: - self.logger.info("Bulk erase failed with errors:") - self.logger.debug("Erase results = %s" % erase_results) - self.logger.info("Attempting individual erase for each package.") - pkgspec_list = [] - for pkg in packages: - pkg_modified = False - for inst in pkg: - if pkg.get('name') != 'gpg-pubkey': - pkgspec = { 'name':pkg.get('name'), - 'epoch':inst.get('epoch', None), - 'version':inst.get('version'), - 'release':inst.get('release'), - 'arch':inst.get('arch') } - pkgspec_list.append(pkgspec) - else: - pkgspec = { 'name':pkg.get('name'), - 'version':inst.get('version'), - 'release':inst.get('release')} - self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ - % (pkgspec.get('name'), self.str_evra(pkgspec))) - self.logger.info(" This package will be deleted in a future version of the RPMng driver.") - continue # Don't delete the gpg-pubkey packages for now. - erase_results = rpmtools.rpm_erase([pkgspec], self.erase_flags) - if erase_results == []: - pkg_modified = True - self.logger.info("Deleted %s %s" % \ - (pkgspec.get('name'), self.str_evra(pkgspec))) - else: - self.logger.error("unable to delete %s %s" % \ - (pkgspec.get('name'), self.str_evra(pkgspec))) - self.logger.debug("Failure = %s" % erase_results) - if pkg_modified == True: - self.modified.append(pkg) - - self.RefreshPackages() - self.extra = self.FindExtraPackages() - - def FixInstance(self, instance, inst_status): - """ - Control if a reinstall of a package happens or not based on the - results from RPMng.VerifyPackage(). - - Return True to reinstall, False to not reintstall. - - """ - fix = False - - if inst_status.get('installed', False) == False: - if instance.get('installed_action', 'install') == "install" and \ - self.installed_action == "install": - fix = True - else: - self.logger.debug('Installed Action for %s %s is to not install' % \ - (inst_status.get('pkg').get('name'), - self.str_evra(instance))) - - elif inst_status.get('version_fail', False) == True: - if instance.get('version_fail_action', 'upgrade') == "upgrade" and \ - self.version_fail_action == "upgrade": - fix = True - else: - self.logger.debug('Version Fail Action for %s %s is to not upgrade' % \ - (inst_status.get('pkg').get('name'), - self.str_evra(instance))) - - elif inst_status.get('verify_fail', False) == True and self.name == "RPMng": - # yum can't reinstall packages so only do this for rpm. - if instance.get('verify_fail_action', 'reinstall') == "reinstall" and \ - self.verify_fail_action == "reinstall": - for inst in inst_status.get('verify'): - # This needs to be a for loop rather than a straight get() - # because the underlying routines handle multiple packages - # and return a list of results. - self.logger.debug('reinstall_check: %s %s:%s-%s.%s' % inst.get('nevra')) - - if inst.get("hdr", False): - fix = True - - elif inst.get('files', False): - # Parse rpm verify file results - for file_result in inst.get('files', []): - self.logger.debug('reinstall_check: file: %s' % file_result) - if file_result[-2] != 'c': - fix = True - break - - # Shouldn't really need this, but included for clarity. - elif inst.get("deps", False): - fix = False - else: - self.logger.debug('Verify Fail Action for %s %s is to not reinstall' % \ - (inst_status.get('pkg').get('name'), - self.str_evra(instance))) - - return fix - - def Install(self, packages, states): - """ - Try and fix everything that RPMng.VerifyPackages() found wrong for - each Package Entry. This can result in individual RPMs being - installed (for the first time), reinstalled, deleted, downgraded - or upgraded. - - packages is a list of Package Elements that has - states[<Package Element>] == False - - The following effects occur: - - states{} is conditionally updated for each package. - - self.installed{} is rebuilt, possibly multiple times. - - self.instance_statusi{} is conditionally updated for each instance - of a package. - - Each package will be added to self.modified[] if its states{} - entry is set to True. - - """ - self.logger.info('Runing RPMng.Install()') - - install_only_pkgs = [] - gpg_keys = [] - upgrade_pkgs = [] - - # Remove extra instances. - # Can not reverify because we don't have a package entry. - if len(self.extra_instances) > 0: - if (self.setup.get('remove') == 'all' or \ - self.setup.get('remove') == 'packages') and\ - not self.setup.get('dryrun'): - self.RemovePackages(self.extra_instances) - else: - self.logger.info("The following extra package instances will be removed by the '-r' option:") - for pkg in self.extra_instances: - for inst in pkg: - self.logger.info(" %s %s" % (pkg.get('name'), self.str_evra(inst))) - - # Figure out which instances of the packages actually need something - # doing to them and place in the appropriate work 'queue'. - for pkg in packages: - for inst in [instn for instn in pkg if instn.tag \ - in ['Instance', 'Package']]: - if self.FixInstance(inst, self.instance_status[inst]): - if pkg.get('name') == 'gpg-pubkey': - gpg_keys.append(inst) - elif pkg.get('name') in self.installOnlyPkgs: - install_only_pkgs.append(inst) - else: - upgrade_pkgs.append(inst) - - # Fix installOnlyPackages - if len(install_only_pkgs) > 0: - self.logger.info("Attempting to install 'install only packages'") - install_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) \ - for inst in install_only_pkgs]) - self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) - cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ - install_args) - if cmdrc == 0: - # The rpm command succeeded. All packages installed. - self.logger.info("Single Pass for InstallOnlyPkgs Succeded") - self.RefreshPackages() - - else: - # The rpm command failed. No packages installed. - # Try installing instances individually. - self.logger.error("Single Pass for InstallOnlyPackages Failed") - installed_instances = [] - for inst in install_only_pkgs: - install_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - self.logger.debug("rpm --install --quiet --oldpackage %s" % install_args) - cmdrc, output = self.cmd.run("rpm --install --quiet --oldpackage --replacepkgs %s" % \ - install_args) - if cmdrc == 0: - installed_instances.append(inst) - else: - self.logger.debug("InstallOnlyPackage %s %s would not install." % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - - install_pkg_set = set([self.instance_status[inst].get('pkg') \ - for inst in install_only_pkgs]) - self.RefreshPackages() - - # Install GPG keys. - if len(gpg_keys) > 0: - for inst in gpg_keys: - self.logger.info("Installing GPG keys.") - key_arg = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - cmdrc, output = self.cmd.run("rpm --import %s" % key_arg) - if cmdrc != 0: - self.logger.debug("Unable to install %s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - else: - self.logger.debug("Installed %s-%s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), \ - inst.get('version'), inst.get('release'))) - self.RefreshPackages() - self.gpg_keyids = self.getinstalledgpg() - pkg = self.instance_status[gpg_keys[0]].get('pkg') - states[pkg] = self.VerifyPackage(pkg, []) - - # Fix upgradeable packages. - if len(upgrade_pkgs) > 0: - self.logger.info("Attempting to upgrade packages") - upgrade_args = " ".join([os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) \ - for inst in upgrade_pkgs]) - cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ - upgrade_args) - if cmdrc == 0: - # The rpm command succeeded. All packages upgraded. - self.logger.info("Single Pass for Upgraded Packages Succeded") - upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ - for inst in upgrade_pkgs]) - self.RefreshPackages() - else: - # The rpm command failed. No packages upgraded. - # Try upgrading instances individually. - self.logger.error("Single Pass for Upgrading Packages Failed") - upgraded_instances = [] - for inst in upgrade_pkgs: - upgrade_args = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - #self.logger.debug("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % \ - # upgrade_args) - cmdrc, output = self.cmd.run("rpm --upgrade --quiet --oldpackage --replacepkgs %s" % upgrade_args) - if cmdrc == 0: - upgraded_instances.append(inst) - else: - self.logger.debug("Package %s %s would not upgrade." % \ - (self.instance_status[inst].get('pkg').get('name'), \ - self.str_evra(inst))) - - upgrade_pkg_set = set([self.instance_status[inst].get('pkg') \ - for inst in upgrade_pkgs]) - self.RefreshPackages() - - if not self.setup['kevlar']: - for pkg_entry in packages: - self.logger.debug("Reverifying Failed Package %s" % (pkg_entry.get('name'))) - states[pkg_entry] = self.VerifyPackage(pkg_entry, \ - self.modlists.get(pkg_entry, [])) - - for entry in [ent for ent in packages if states[ent]]: - self.modified.append(entry) - - def canInstall(self, entry): - """Test if entry has enough information to be installed.""" - if not self.handlesEntry(entry): - return False - - if 'failure' in entry.attrib: - self.logger.error("Cannot install entry %s:%s with bind failure" % \ - (entry.tag, entry.get('name'))) - return False - - - instances = entry.findall('Instance') - - # If the entry wasn't verifiable, then we really don't want to try and fix something - # that we don't know is broken. - if not self.canVerify(entry): - self.logger.debug("WARNING: Package %s was not verifiable, not passing to Install()" \ - % entry.get('name')) - return False - - if not instances: - # Old non Instance format, unmodified. - if entry.get('name') == 'gpg-pubkey': - # gpg-pubkey packages aren't really pacakges, so we have to do - # something a little different. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__gpg_ireq__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot install" \ - % (entry.tag, entry.get('name'))) - return False - else: - if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot install" \ - % (entry.tag, entry.get('name'))) - return False - else: - if entry.get('name') == 'gpg-pubkey': - # gpg-pubkey packages aren't really pacakges, so we have to do - # something a little different. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__new_gpg_ireq__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot install" \ - % (entry.tag, entry.get('name'))) - return False - # Check that the Instance Level has what we need for verification. - for inst in instances: - if [attr for attr in self.__new_gpg_ireq__[inst.tag] \ - if attr not in inst.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot install"\ - % (inst.tag, entry.get('name'))) - return False - else: - # New format with Instances. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__new_ireq__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot install" \ - % (entry.tag, entry.get('name'))) - self.logger.error(" Required attributes that may not be present are %s" \ - % (self.__new_ireq__[entry.tag])) - return False - # Check that the Instance Level has what we need for verification. - for inst in instances: - if inst.tag == 'Instance': - if [attr for attr in self.__new_ireq__[inst.tag] \ - if attr not in inst.attrib]: - self.logger.error("Incomplete information for %s of package %s; cannot install" \ - % (inst.tag, entry.get('name'))) - self.logger.error(" Required attributes that may not be present are %s" \ - % (self.__new_ireq__[inst.tag])) - return False - return True - - def canVerify(self, entry): - """ - Test if entry has enough information to be verified. - - Three types of entries are checked. - Old style Package - New style Package with Instances - pgp-pubkey packages - - Also the old style entries get modified after the first - VerifyPackage() run, so there needs to be a second test. - - """ - if not self.handlesEntry(entry): - return False - - if 'failure' in entry.attrib: - self.logger.error("Entry %s:%s reports bind failure: %s" % \ - (entry.tag, entry.get('name'), entry.get('failure'))) - return False - - # We don't want to do any checks so we don't care what the entry has in it. - if (not self.pkg_checks or - entry.get('pkg_checks', 'true').lower() == 'false'): - return True - - instances = entry.findall('Instance') - - if not instances: - # Old non Instance format, unmodified. - if entry.get('name') == 'gpg-pubkey': - # gpg-pubkey packages aren't really pacakges, so we have to do - # something a little different. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__gpg_req__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (entry.tag, entry.get('name'))) - return False - elif entry.tag == 'Path' and entry.get('type') == 'ignore': - # ignored Paths are only relevant during failed package - # verification - pass - else: - if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (entry.tag, entry.get('name'))) - return False - else: - if entry.get('name') == 'gpg-pubkey': - # gpg-pubkey packages aren't really pacakges, so we have to do - # something a little different. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__new_gpg_req__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (entry.tag, entry.get('name'))) - return False - # Check that the Instance Level has what we need for verification. - for inst in instances: - if [attr for attr in self.__new_gpg_req__[inst.tag] \ - if attr not in inst.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (inst.tag, inst.get('name'))) - return False - else: - # New format with Instances, or old style modified. - # Check that the Package Level has what we need for verification. - if [attr for attr in self.__new_req__[entry.tag] if attr not in entry.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (entry.tag, entry.get('name'))) - return False - # Check that the Instance Level has what we need for verification. - for inst in instances: - if inst.tag == 'Instance': - if [attr for attr in self.__new_req__[inst.tag] \ - if attr not in inst.attrib]: - self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ - % (inst.tag, inst.get('name'))) - return False - return True - - def FindExtraPackages(self): - """Find extra packages.""" - packages = [entry.get('name') for entry in self.getSupportedEntries()] - extras = [] - - for (name, instances) in list(self.installed.items()): - if name not in packages: - extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) - for installed_inst in instances: - if self.setup['extra']: - self.logger.info("Extra Package %s %s." % \ - (name, self.str_evra(installed_inst))) - tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ - version = installed_inst.get('version'), \ - release = installed_inst.get('release')) - if installed_inst.get('epoch', None) != None: - tmp_entry.set('epoch', str(installed_inst.get('epoch'))) - if installed_inst.get('arch', None) != None: - tmp_entry.set('arch', installed_inst.get('arch')) - extras.append(extra_entry) - return extras - - - def FindExtraInstances(self, pkg_entry, installed_entry): - """ - Check for installed instances that are not in the config. - Return a Package Entry with Instances to remove, or None if there - are no Instances to remove. - - """ - name = pkg_entry.get('name') - extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) - instances = [inst for inst in pkg_entry if inst.tag == 'Instance' or inst.tag == 'Package'] - if name in self.installOnlyPkgs: - for installed_inst in installed_entry: - not_found = True - for inst in instances: - if self.pkg_vr_equal(inst, installed_inst) or \ - self.inst_evra_equal(inst, installed_inst): - not_found = False - break - if not_found == True: - # Extra package. - self.logger.info("Extra InstallOnlyPackage %s %s." % \ - (name, self.str_evra(installed_inst))) - tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ - version = installed_inst.get('version'), \ - release = installed_inst.get('release')) - if installed_inst.get('epoch', None) != None: - tmp_entry.set('epoch', str(installed_inst.get('epoch'))) - if installed_inst.get('arch', None) != None: - tmp_entry.set('arch', installed_inst.get('arch')) - else: - # Normal package, only check arch. - for installed_inst in installed_entry: - not_found = True - for inst in instances: - if installed_inst.get('arch', None) == inst.get('arch', None) or\ - inst.tag == 'Package': - not_found = False - break - if not_found: - self.logger.info("Extra Normal Package Instance %s %s" % \ - (name, self.str_evra(installed_inst))) - tmp_entry = Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', \ - version = installed_inst.get('version'), \ - release = installed_inst.get('release')) - if installed_inst.get('epoch', None) != None: - tmp_entry.set('epoch', str(installed_inst.get('epoch'))) - if installed_inst.get('arch', None) != None: - tmp_entry.set('arch', installed_inst.get('arch')) - - if len(extra_entry) == 0: - extra_entry = None - - return extra_entry - - def str_evra(self, instance): - """Convert evra dict entries to a string.""" - if instance.get('epoch', '*') in ['*', None]: - return '%s-%s.%s' % (instance.get('version', '*'), - instance.get('release', '*'), - instance.get('arch', '*')) - else: - return '%s:%s-%s.%s' % (instance.get('epoch', '*'), - instance.get('version', '*'), - instance.get('release', '*'), - instance.get('arch', '*')) - - def pkg_vr_equal(self, config_entry, installed_entry): - ''' - Compare old style entry to installed entry. Which means ignore - the epoch and arch. - ''' - if (config_entry.tag == 'Package' and \ - config_entry.get('version') == installed_entry.get('version') and \ - config_entry.get('release') == installed_entry.get('release')): - return True - else: - return False - - def inst_evra_equal(self, config_entry, installed_entry): - """Compare new style instance to installed entry.""" - - if config_entry.get('epoch', None) != None: - epoch = int(config_entry.get('epoch')) - else: - epoch = None - - if (config_entry.tag == 'Instance' and \ - (epoch == installed_entry.get('epoch', 0) or \ - (epoch == 0 and installed_entry.get('epoch', 0) == None) or \ - (epoch == None and installed_entry.get('epoch', 0) == 0)) and \ - config_entry.get('version') == installed_entry.get('version') and \ - config_entry.get('release') == installed_entry.get('release') and \ - config_entry.get('arch', None) == installed_entry.get('arch', None)): - return True - else: - return False - - def getinstalledgpg(self): - """ - Create a list of installed GPG key IDs. - - The pgp-pubkey package version is the least significant 4 bytes - (big-endian) of the key ID which is good enough for our purposes. - - """ - init_ts = rpmtools.rpmtransactionset() - init_ts.setVSFlags(rpm._RPMVSF_NODIGESTS|rpm._RPMVSF_NOSIGNATURES) - gpg_hdrs = rpmtools.getheadersbykeyword(init_ts, **{'name':'gpg-pubkey'}) - keyids = [ header[rpm.RPMTAG_VERSION] for header in gpg_hdrs] - keyids.append('None') - init_ts.closeDB() - del init_ts - return keyids - - def VerifyPath(self, entry, _): - """ - We don't do anything here since all - Paths are processed in __init__ - """ - return True +class RPMng(RPM): + """ RPM driver called 'RPMng' for backwards compat """ + deprecated = True diff --git a/src/lib/Bcfg2/Client/Tools/YUM.py b/src/lib/Bcfg2/Client/Tools/YUM.py new file mode 100644 index 000000000..8a2ba6f87 --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/YUM.py @@ -0,0 +1,951 @@ +"""This provides bcfg2 support for yum.""" + +import copy +import os.path +import sys +import yum +import yum.packages +import yum.rpmtrans +import yum.callbacks +import yum.Errors +import yum.misc +import rpmUtils.arch +import Bcfg2.Client.XML +import Bcfg2.Client.Tools + + +def build_yname(pkgname, inst): + """Build yum appropriate package name.""" + d = {} + if isinstance(inst, yum.packages.PackageObject): + for i in ['name', 'epoch', 'version', 'release', 'arch']: + d[i] = getattr(inst, i) + else: + d['name'] = pkgname + if inst.get('version') != 'any': + d['version'] = inst.get('version') + if inst.get('epoch', False): + d['epoch'] = inst.get('epoch') + if inst.get('release', False) and inst.get('release') != 'any': + d['release'] = inst.get('release') + if inst.get('arch', False) and inst.get('arch') != 'any': + d['arch'] = inst.get('arch') + return d + + +def short_yname(nevra): + d = nevra.copy() + if 'version' in d: + d['ver'] = d['version'] + del d['version'] + if 'release' in d: + d['rel'] = d['release'] + del d['release'] + return d + + +def nevraString(p): + if isinstance(p, yum.packages.PackageObject): + return str(p) + else: + ret = "" + for i, j in [('epoch', '%s:'), ('name', '%s'), ('version', '-%s'), + ('release', '-%s'), ('arch', '.%s')]: + if i in p: + ret = "%s%s" % (ret, j % p[i]) + return ret + + +class RPMDisplay(yum.rpmtrans.RPMBaseCallback): + """We subclass the default RPM transaction callback so that we + can control Yum's verbosity and pipe it through the right logger.""" + + def __init__(self, logger): + yum.rpmtrans.RPMBaseCallback.__init__(self) + self.logger = logger + self.state = None + self.package = None + + def event(self, package, action, te_current, te_total, + ts_current, ts_total): + """ + @param package: A yum package object or simple string of a package name + @param action: A yum.constant transaction set state or in the obscure + rpm repackage case it could be the string 'repackaging' + @param te_current: Current number of bytes processed in the transaction + element being processed + @param te_total: Total number of bytes in the transaction element being + processed + @param ts_current: number of processes completed in whole transaction + @param ts_total: total number of processes in the transaction. + """ + + if self.package != str(package) or action != self.state: + msg = "%s: %s" % (self.action[action], package) + self.logger.info(msg) + self.state = action + self.package = str(package) + + def scriptout(self, package, msgs): + """Handle output from package scripts.""" + + if msgs: + msg = "%s: %s" % (package, msgs) + self.logger.debug(msg) + + def errorlog(self, msg): + """Deal with error reporting.""" + self.logger.error(msg) + + +class YumDisplay(yum.callbacks.ProcessTransBaseCallback): + """Class to handle display of what step we are in the Yum transaction + such as downloading packages, etc.""" + + def __init__(self, logger): + self.logger = logger + + +class YUM(Bcfg2.Client.Tools.PkgTool): + """Support for Yum packages.""" + pkgtype = 'yum' + __execs__ = [] + __handles__ = [('Package', 'yum'), + ('Package', 'rpm'), + ('Path', 'ignore')] + + __req__ = {'Package': ['name'], + 'Path': ['type']} + __ireq__ = {'Package': ['name']} + + conflicts = ['YUM24', 'RPM', 'RPMng', 'YUMng'] + + def __init__(self, logger, setup, config): + self._loadYumBase(setup=setup, logger=logger) + Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) + self.ignores = [entry.get('name') for struct in config \ + for entry in struct \ + if entry.tag == 'Path' and \ + entry.get('type') == 'ignore'] + self.instance_status = {} + self.extra_instances = [] + self.modlists = {} + self._loadConfig() + self.__important__ = self.__important__ + \ + [entry.get('name') for struct in config \ + for entry in struct \ + if entry.tag == 'Path' and \ + (entry.get('name').startswith('/etc/yum.d') \ + or entry.get('name').startswith('/etc/yum.repos.d')) \ + or entry.get('name') == '/etc/yum.conf'] + self.yum_avail = dict() + self.yum_installed = dict() + + yup = self.yb.doPackageLists(pkgnarrow='updates') + if hasattr(self.yb.rpmdb, 'pkglist'): + yinst = self.yb.rpmdb.pkglist + else: + yinst = self.yb.rpmdb.getPkgList() + for dest, source in [(self.yum_avail, yup.updates), + (self.yum_installed, yinst)]: + for pkg in source: + if dest is self.yum_avail: + pname = pkg.name + data = [(pkg.arch, (pkg.epoch, pkg.version, pkg.release))] + else: + pname = pkg[0] + data = [(pkg[1], (pkg[2], pkg[3], pkg[4]))] + if pname in dest: + dest[pname].update(data) + else: + dest[pname] = dict(data) + + def _loadYumBase(self, setup=None, logger=None): + ''' this may be called before PkgTool.__init__() is called on + this object (when the YUM object is first instantiated; + PkgTool.__init__() calls RefreshPackages(), which requires a + YumBase object already exist), or after __init__() has + completed, when we reload the yum config before installing + packages. Consequently, we support both methods by allowing + setup and logger, the only object properties we use in this + function, to be passed as keyword arguments or to be omitted + and drawn from the object itself.''' + self.yb = yum.YumBase() + + if setup is None: + setup = self.setup + if logger is None: + logger = self.logger + + if setup['debug']: + debuglevel = 3 + elif setup['verbose']: + debuglevel = 2 + else: + debuglevel = 0 + + # pylint: disable=E1121 + try: + self.yb.preconf.debuglevel = debuglevel + self.yb._getConfig() + except AttributeError: + self.yb._getConfig(self.yb.conf.config_file_path, + debuglevel=debuglevel) + # pylint: enable=E1121 + + try: + self.yb.doConfigSetup() + self.yb.doTsSetup() + self.yb.doRpmDBSetup() + except yum.Errors.RepoError: + err = sys.exc_info()[1] + logger.error("YUM Repository error: %s" % err) + raise Bcfg2.Client.Tools.ToolInstantiationError + except Exception: + err = sys.exc_info()[1] + logger.error("Yum error: %s" % err) + raise Bcfg2.Client.Tools.ToolInstantiationError + + def _loadConfig(self): + # Process the Yum section from the config file. + # These are all boolean flags, either we do stuff or we don't + self.pkg_checks = self.setup["yum_pkg_checks"] + self.pkg_verify = self.setup["yum_pkg_verify"] + self.doInstall = self.setup["yum_installed_action"] == "install" + self.doUpgrade = self.setup["yum_version_fail_action"] == "upgrade" + self.doReinst = self.setup["yum_verify_fail_action"] == "reinstall" + self.verifyFlags = self.setup["yum_verify_flags"] + + self.installOnlyPkgs = self.yb.conf.installonlypkgs + if 'gpg-pubkey' not in self.installOnlyPkgs: + self.installOnlyPkgs.append('gpg-pubkey') + + self.logger.debug("Yum: Install missing: %s" % self.doInstall) + self.logger.debug("Yum: pkg_checks: %s" % self.pkg_checks) + self.logger.debug("Yum: pkg_verify: %s" % self.pkg_verify) + self.logger.debug("Yum: Upgrade on version fail: %s" % self.doUpgrade) + self.logger.debug("Yum: Reinstall on verify fail: %s" % self.doReinst) + self.logger.debug("Yum: installOnlyPkgs: %s" % self.installOnlyPkgs) + self.logger.debug("Yum: verify_flags: %s" % self.verifyFlags) + + def _fixAutoVersion(self, entry): + # old style entry; synthesize Instances from current installed + if entry.get('name') not in self.yum_installed and \ + entry.get('name') not in self.yum_avail: + # new entry; fall back to default + entry.set('version', 'any') + else: + data = copy.copy(self.yum_installed[entry.get('name')]) + if entry.get('name') in self.yum_avail: + # installed but out of date + data.update(self.yum_avail[entry.get('name')]) + for (arch, (epoch, vers, rel)) in list(data.items()): + x = Bcfg2.Client.XML.SubElement(entry, "Instance", + name=entry.get('name'), + version=vers, arch=arch, + release=rel, epoch=epoch) + if 'verify_flags' in entry.attrib: + x.set('verify_flags', entry.get('verify_flags')) + if 'verify' in entry.attrib: + x.set('verify', entry.get('verify')) + + def _buildInstances(self, entry): + instances = [inst for inst in entry \ + if inst.tag == 'Instance' or inst.tag == 'Package'] + + # XXX: Uniquify instances. Cases where duplicates are returned. + # However, the elements aren't comparable. + + if instances == []: + # We have an old style no Instance entry. Convert it to new style. + instance = Bcfg2.Client.XML.SubElement(entry, 'Package') + for attrib in list(entry.attrib.keys()): + instance.attrib[attrib] = entry.attrib[attrib] + instances = [instance] + + return instances + + def _getGPGKeysAsPackages(self): + """Return a list of the GPG RPM signing keys installed on the + system as a list of Package Objects.""" + + # XXX GPG keys existing in the RPMDB have numbered days + # and newer Yum versions will not return information about them + if hasattr(self.yb.rpmdb, 'returnGPGPubkeyPackages'): + return self.yb.rpmdb.returnGPGPubkeyPackages() + return self.yb.rpmdb.searchNevra(name='gpg-pubkey') + + def _verifyHelper(self, po): + # This code primarly deals with a yum bug where the PO.verify() + # method does not properly take into count multilib sharing of files. + # Neither does RPM proper, really....it just ignores the problem. + def verify(p): + # disabling file checksums is a new feature yum 3.2.17-ish + try: + vResult = p.verify(fast=self.setup.get('quick', False)) + except TypeError: + # Older Yum API + vResult = p.verify() + return vResult + + key = (po.name, po.epoch, po.version, po.release, po.arch) + if key in self.verifyCache: + results = self.verifyCache[key] + else: + results = verify(po) + self.verifyCache[key] = results + if not rpmUtils.arch.isMultiLibArch(): + return results + + # Okay deal with a buggy yum multilib and verify + packages = self.yb.rpmdb.searchNevra(name=po.name, epoch=po.epoch, + ver=po.version, rel=po.release) # find all arches of pkg + if len(packages) == 1: + return results # No mathcing multilib packages + + files = set(po.returnFileEntries()) # Will be the list of common fns + common = {} + for p in packages: + if p != po: + files = files & set(p.returnFileEntries()) + for p in packages: + k = (p.name, p.epoch, p.version, p.release, p.arch) + self.logger.debug("Multilib Verify: comparing %s to %s" \ + % (po, p)) + if k in self.verifyCache: + v = self.verifyCache[k] + else: + v = verify(p) + self.verifyCache[k] = v + + for fn, probs in list(v.items()): + # file problems must exist in ALL multilib packages to be real + if fn in files: + common[fn] = common.get(fn, 0) + 1 + + flag = len(packages) - 1 + for fn, i in list(common.items()): + if i == flag: + # this fn had verify problems in all but one of the multilib + # packages. That means its correct in the package that's + # "on top." Therefore, this is a fake verify problem. + if fn in results: + del results[fn] + + return results + + def RefreshPackages(self): + """ + Creates self.installed{} which is a dict of installed packages. + + The dict items are lists of nevra dicts. This loosely matches the + config from the server and what rpmtools uses to specify pacakges. + + e.g. + + self.installed['foo'] = [ {'name':'foo', 'epoch':None, + 'version':'1', 'release':2, + 'arch':'i386'}, + {'name':'foo', 'epoch':None, + 'version':'1', 'release':2, + 'arch':'x86_64'} ] + """ + + self.installed = {} + packages = self._getGPGKeysAsPackages() + \ + self.yb.rpmdb.returnPackages() + for po in packages: + d = {} + for i in ['name', 'epoch', 'version', 'release', 'arch']: + if i == 'arch' and getattr(po, i) is None: + d[i] = 'noarch' + elif i == 'epoch' and getattr(po, i) is None: + d[i] = '0' + else: + d[i] = getattr(po, i) + self.installed.setdefault(po.name, []).append(d) + + def VerifyPackage(self, entry, modlist, pinned_version=None): + """ + Verify Package status for entry. + Performs the following: + - Checks for the presence of required Package Instances. + - Compares the evra 'version' info against self.installed{}. + - RPM level package verify (rpm --verify). + - Checks for the presence of unrequired package instances. + + Produces the following dict and list for Yum.Install() to use: + For installs/upgrades/fixes of required instances: + instance_status = { <Instance Element Object>: + { 'installed': True|False, + 'version_fail': True|False, + 'verify_fail': True|False, + 'pkg': <Package Element Object>, + 'modlist': [ <filename>, ... ], + 'verify' : [ <rpm --verify results> ] + }, ...... + } + + For deletions of unrequired instances: + extra_instances = [ <Package Element Object>, ..... ] + + Constructs the text prompts for interactive mode. + """ + + if entry.get('version', False) == 'auto': + self._fixAutoVersion(entry) + + self.logger.debug("Verifying package instances for %s" % + entry.get('name')) + + self.verifyCache = {} # Used for checking multilib packages + self.modlists[entry] = modlist + instances = self._buildInstances(entry) + packageCache = [] + package_fail = False + qtext_versions = [] + virtPkg = False + pkg_checks = self.pkg_checks and \ + entry.get('pkg_checks', 'true').lower() == 'true' + pkg_verify = self.pkg_verify and \ + entry.get('pkg_verify', 'true').lower() == 'true' + + if entry.get('name') == 'gpg-pubkey': + POs = self._getGPGKeysAsPackages() + pkg_verify = False # No files here to verify + else: + POs = self.yb.rpmdb.searchNevra(name=entry.get('name')) + if len(POs) == 0: + # Some sort of virtual capability? Try to resolve it + POs = self.yb.rpmdb.searchProvides(entry.get('name')) + if len(POs) > 0: + virtPkg = True + self.logger.info("%s appears to be provided by:" % + entry.get('name')) + for p in POs: + self.logger.info(" %s" % p) + + for inst in instances: + nevra = build_yname(entry.get('name'), inst) + snevra = short_yname(nevra) + if nevra in packageCache: + continue # Ignore duplicate instances + else: + packageCache.append(nevra) + + self.logger.debug("Verifying: %s" % nevraString(nevra)) + + # Set some defaults here + stat = self.instance_status.setdefault(inst, {}) + stat['installed'] = True + stat['version_fail'] = False + stat['verify'] = {} + stat['verify_fail'] = False + stat['pkg'] = entry + stat['modlist'] = modlist + if inst.get('verify_flags'): + # this splits on either space or comma + verify_flags = \ + inst.get('verify_flags').lower().replace(' ', + ',').split(',') + else: + verify_flags = self.verifyFlags + + if 'arch' in nevra: + # If arch is specified use it to select the package + _POs = [ p for p in POs if p.arch == nevra['arch'] ] + else: + _POs = POs + if len(_POs) == 0: + # Package (name, arch) not installed + entry.set('current_exists', 'false') + self.logger.debug(" %s is not installed" % nevraString(nevra)) + stat['installed'] = False + package_fail = True + qtext_versions.append("I(%s)" % nevra) + continue + + if not pkg_checks: + continue + + # Check EVR + if virtPkg: + # we need to make sure that the version of the symbol + # provided matches the one required in the + # configuration + vlist = [] + for attr in ["epoch", "version", "release"]: + vlist.append(nevra.get(attr)) + if tuple(vlist) == (None, None, None): + # we just require the package name, no particular + # version, so just make a copy of POs since every + # package that provides this symbol satisfies the + # requirement + _POs = [po for po in POs] + else: + _POs = [po for po in POs + if po.checkPrco('provides', + (nevra["name"], 'EQ', + tuple(vlist)))] + elif entry.get('name') == 'gpg-pubkey': + if 'version' not in nevra: + m = "Skipping verify: gpg-pubkey without an RPM version." + self.logger.warning(m) + continue + if 'release' not in nevra: + m = "Skipping verify: gpg-pubkey without an RPM release." + self.logger.warning(m) + continue + _POs = [p for p in POs if p.version == nevra['version'] \ + and p.release == nevra['release']] + else: + _POs = self.yb.rpmdb.searchNevra(**snevra) + if len(_POs) == 0: + package_fail = True + stat['version_fail'] = True + # Just chose the first pkg for the error message + if virtPkg: + provTuple = \ + [p for p in POs[0].provides + if p[0] == entry.get("name")][0] + entry.set('current_version', "%s:%s-%s" % provTuple[2]) + self.logger.info(" %s: Wrong version installed. " + "Want %s, but %s provides %s" % + (entry.get("name"), + nevraString(nevra), + nevraString(POs[0]), + yum.misc.prco_tuple_to_string(provTuple))) + else: + entry.set('current_version', "%s:%s-%s.%s" % + (POs[0].epoch, + POs[0].version, + POs[0].release, + POs[0].arch)) + self.logger.info(" %s: Wrong version installed. " + "Want %s, but have %s" % + (entry.get("name"), + nevraString(nevra), + nevraString(POs[0]))) + entry.set('version', "%s:%s-%s.%s" % + (nevra.get('epoch', 'any'), + nevra.get('version', 'any'), + nevra.get('release', 'any'), + nevra.get('arch', 'any'))) + qtext_versions.append("U(%s)" % str(POs[0])) + continue + + if self.setup.get('quick', False): + # Passed -q on the command line + continue + if not (pkg_verify and \ + inst.get('pkg_verify', 'true').lower() == 'true'): + continue + + # XXX: We ignore GPG sig checking the package as it + # has nothing to do with the individual file hash/size/etc. + # GPG checking the package only eaxmines some header/rpmdb + # wacky-ness, and will not properly detect a compromised rpmdb. + # Yum's verify routine does not support it for that reaosn. + + if len(_POs) > 1: + self.logger.debug(" Verify Instance found many packages:") + for po in _POs: + self.logger.debug(" %s" % str(po)) + + try: + vResult = self._verifyHelper(_POs[0]) + except Exception: + e = sys.exc_info()[1] + # Unknown Yum exception + self.logger.warning(" Verify Exception: %s" % str(e)) + package_fail = True + continue + + # Now take out the Yum specific objects / modlists / unproblems + ignores = [ig.get('name') for ig in entry.findall('Ignore')] + \ + [ig.get('name') for ig in inst.findall('Ignore')] + \ + self.ignores + for fn, probs in list(vResult.items()): + if fn in modlist: + self.logger.debug(" %s in modlist, skipping" % fn) + continue + if fn in ignores: + self.logger.debug(" %s in ignore list, skipping" % fn) + continue + tmp = [] + for p in probs: + if p.type == 'missing' and os.path.islink(fn): + continue + elif 'no' + p.type in verify_flags: + continue + if p.type not in ['missingok', 'ghost']: + tmp.append((p.type, p.message)) + if tmp != []: + stat['verify'][fn] = tmp + + if stat['verify'] != {}: + stat['verify_fail'] = True + package_fail = True + self.logger.debug("It is suggested that you either manage " + "these files, revert the changes, or ignore " + "false failures:") + self.logger.debug(" Verify Problems:") + for fn, probs in list(stat['verify'].items()): + self.logger.debug(" %s" % fn) + for p in probs: + self.logger.debug(" %s: %s" % p) + + if len(POs) > 0: + # Is this an install only package? We just look at the first one + provides = set([p[0] for p in POs[0].provides] + [POs[0].name]) + install_only = len(set(self.installOnlyPkgs) & provides) > 0 + else: + install_only = False + + if virtPkg or (install_only and not self.setup['kevlar']): + # XXX: virtual capability supplied, we a probably dealing + # with multiple packages of different names. This check + # doesn't make a lot of since in this case + # XXX: install_only: Yum may clean some of these up itself. + # Otherwise having multiple instances of install only packages + # is considered correct + self.extra_instances = None + else: + self.extra_instances = self.FindExtraInstances(entry, POs) + if self.extra_instances is not None: + package_fail = True + + return not package_fail + + def FindExtraInstances(self, entry, POs): + """ + Check for installed instances that are not in the config. + Return a Package Entry with Instances to remove, or None if there + are no Instances to remove. + + """ + if len(POs) == 0: + return None + name = entry.get('name') + extra_entry = Bcfg2.Client.XML.Element('Package', name=name, + type=self.pkgtype) + instances = self._buildInstances(entry) + _POs = [p for p in POs] # Shallow copy + + # Algorythm is sensitive to duplicates, check for them + checked = [] + for inst in instances: + nevra = build_yname(name, inst) + snevra = short_yname(nevra) + pkgs = self.yb.rpmdb.searchNevra(**snevra) + flag = True + if len(pkgs) > 0: + if pkgs[0] in checked: + continue # We've already taken care of this Instance + else: + checked.append(pkgs[0]) + _POs.remove(pkgs[0]) + + for p in _POs: + self.logger.debug(" Extra Instance Found: %s" % str(p)) + Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', + epoch=p.epoch, name=p.name, version=p.version, + release=p.release, arch=p.arch) + + if _POs == []: + return None + else: + return extra_entry + + def FindExtraPackages(self): + """Find extra packages.""" + packages = [e.get('name') for e in self.getSupportedEntries()] + extras = [] + + for p in list(self.installed.keys()): + if p not in packages: + entry = Bcfg2.Client.XML.Element('Package', name=p, + type=self.pkgtype) + for i in self.installed[p]: + inst = Bcfg2.Client.XML.SubElement(entry, + 'Instance', + epoch=i['epoch'], + version=i['version'], + release=i['release'], + arch=i['arch']) + + extras.append(entry) + + return extras + + def _installGPGKey(self, inst, key_file): + """Examine the GPG keys carefully before installation. Avoid + installing duplicate keys. Returns True on successful install.""" + + # RPM Transaction Set + ts = self.yb.rpmdb.readOnlyTS() + + if not os.path.exists(key_file): + self.logger.debug("GPG Key file %s not installed" % key_file) + return False + + rawkey = open(key_file).read() + gpg = yum.misc.getgpgkeyinfo(rawkey) + + ver = yum.misc.keyIdToRPMVer(gpg['keyid']) + rel = yum.misc.keyIdToRPMVer(gpg['timestamp']) + if not (ver == inst.get('version') and rel == inst.get('release')): + self.logger.info("GPG key file %s does not match gpg-pubkey-%s-%s"\ + % (key_file, inst.get('version'), + inst.get('release'))) + return False + + if not yum.misc.keyInstalled(ts, gpg['keyid'], + gpg['timestamp']) == 0: + result = ts.pgpImportPubkey(yum.misc.procgpgkey(rawkey)) + else: + self.logger.debug("gpg-pubkey-%s-%s already installed"\ + % (inst.get('version'), + inst.get('release'))) + return True + + if result != 0: + self.logger.debug("Unable to install %s-%s" % \ + (self.instance_status[inst].get('pkg').get('name'), + nevraString(inst))) + return False + else: + self.logger.debug("Installed %s-%s-%s" % \ + (self.instance_status[inst].get('pkg').get('name'), + inst.get('version'), inst.get('release'))) + return True + + def _runYumTransaction(self): + def cleanup(): + self.yb.closeRpmDB() + self.RefreshPackages() + + rDisplay = RPMDisplay(self.logger) + yDisplay = YumDisplay(self.logger) + # Run the Yum Transaction + try: + rescode, restring = self.yb.buildTransaction() + except yum.Errors.YumBaseError: + e = sys.exc_info()[1] + self.logger.error("Yum transaction error: %s" % str(e)) + cleanup() + return + + self.logger.debug("Initial Yum buildTransaction() run said:") + self.logger.debug(" resultcode: %s, msgs: %s" \ + % (rescode, restring)) + + if rescode != 1: + # Transaction built successfully, run it + try: + self.yb.processTransaction(callback=yDisplay, + rpmDisplay=rDisplay) + self.logger.info("Single Pass for Install Succeeded") + except yum.Errors.YumBaseError: + e = sys.exc_info()[1] + self.logger.error("Yum transaction error: %s" % str(e)) + cleanup() + return + else: + # The yum command failed. No packages installed. + # Try installing instances individually. + self.logger.error("Single Pass Install of Packages Failed") + skipBroken = self.yb.conf.skip_broken + self.yb.conf.skip_broken = True + try: + rescode, restring = self.yb.buildTransaction() + if rescode != 1: + self.yb.processTransaction(callback=yDisplay, + rpmDisplay=rDisplay) + self.logger.debug( + "Second pass install did not install all packages") + else: + self.logger.error("Second pass yum install failed.") + self.logger.debug(" %s" % restring) + except yum.Errors.YumBaseError: + e = sys.exc_info()[1] + self.logger.error("Yum transaction error: %s" % str(e)) + + self.yb.conf.skip_broken = skipBroken + + cleanup() + + def Install(self, packages, states): + """ + Try and fix everything that Yum.VerifyPackages() found wrong for + each Package Entry. This can result in individual RPMs being + installed (for the first time), deleted, downgraded + or upgraded. + + packages is a list of Package Elements that has + states[<Package Element>] == False + + The following effects occur: + - states{} is conditionally updated for each package. + - self.installed{} is rebuilt, possibly multiple times. + - self.instance_status{} is conditionally updated for each instance + of a package. + - Each package will be added to self.modified[] if its states{} + entry is set to True. + + """ + self.logger.debug('Running Yum.Install()') + + install_pkgs = [] + gpg_keys = [] + upgrade_pkgs = [] + reinstall_pkgs = [] + + def queuePkg(pkg, inst, queue): + if pkg.get('name') == 'gpg-pubkey': + gpg_keys.append(inst) + else: + queue.append(inst) + + # Remove extra instances. + # Can not reverify because we don't have a package entry. + if self.extra_instances is not None and len(self.extra_instances) > 0: + if (self.setup.get('remove') == 'all' or \ + self.setup.get('remove') == 'packages'): + self.RemovePackages(self.extra_instances) + else: + self.logger.info("The following extra package instances will be removed by the '-r' option:") + for pkg in self.extra_instances: + for inst in pkg: + self.logger.info(" %s %s" % \ + ((pkg.get('name'), nevraString(inst)))) + + # Figure out which instances of the packages actually need something + # doing to them and place in the appropriate work 'queue'. + for pkg in packages: + insts = [pinst for pinst in pkg \ + if pinst.tag in ['Instance', 'Package']] + if insts: + for inst in insts: + if inst not in self.instance_status: + m = " Asked to install/update package never verified" + p = nevraString(build_yname(pkg.get('name'), inst)) + self.logger.warning("%s: %s" % (m, p)) + continue + status = self.instance_status[inst] + if not status.get('installed', False) and self.doInstall: + queuePkg(pkg, inst, install_pkgs) + elif status.get('version_fail', False) and self.doUpgrade: + queuePkg(pkg, inst, upgrade_pkgs) + elif status.get('verify_fail', False) and self.doReinst: + queuePkg(pkg, inst, reinstall_pkgs) + else: + # Either there was no Install/Version/Verify + # task to be done or the user disabled the actions + # in the configuration. XXX Logging for the latter? + pass + else: + msg = "Yum: Package tag found where Instance expected: %s" + self.logger.warning(msg % pkg.get('name')) + queuePkg(pkg, pkg, install_pkgs) + + # Install GPG keys. + # Alternatively specify the required keys using 'gpgkey' in the + # repository definition in yum.conf. YUM will install the keys + # automatically. + if len(gpg_keys) > 0: + self.logger.info("Installing GPG keys.") + for inst in gpg_keys: + if inst.get('simplefile') is None: + self.logger.error("GPG key has no simplefile attribute") + continue + key_file = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ + inst.get('simplefile')) + self._installGPGKey(inst, key_file) + + self.RefreshPackages() + pkg = self.instance_status[gpg_keys[0]].get('pkg') + states[pkg] = self.VerifyPackage(pkg, []) + + # We want to reload all Yum configuration in case we've + # deployed new .repo files we should consider + self._loadYumBase() + + # Install packages. + if len(install_pkgs) > 0: + self.logger.info("Attempting to install packages") + + for inst in install_pkgs: + pkg_arg = self.instance_status[inst].get('pkg').get('name') + self.logger.debug("Installing %s" % pkg_arg) + try: + self.yb.install(**build_yname(pkg_arg, inst)) + except yum.Errors.YumBaseError: + yume = sys.exc_info()[1] + self.logger.error("Error installing package %s: %s" % + (pkg_arg, yume)) + + if len(upgrade_pkgs) > 0: + self.logger.info("Attempting to upgrade packages") + + for inst in upgrade_pkgs: + pkg_arg = self.instance_status[inst].get('pkg').get('name') + self.logger.debug("Upgrading %s" % pkg_arg) + try: + self.yb.update(**build_yname(pkg_arg, inst)) + except yum.Errors.YumBaseError: + yume = sys.exc_info()[1] + self.logger.error("Error upgrading package %s: %s" % + (pkg_arg, yume)) + + if len(reinstall_pkgs) > 0: + self.logger.info("Attempting to reinstall packages") + for inst in reinstall_pkgs: + pkg_arg = self.instance_status[inst].get('pkg').get('name') + self.logger.debug("Reinstalling %s" % pkg_arg) + try: + self.yb.reinstall(**build_yname(pkg_arg, inst)) + except yum.Errors.YumBaseError: + yume = sys.exc_info()[1] + self.logger.error("Error reinstalling package %s: %s" % + (pkg_arg, yume)) + + self._runYumTransaction() + + if not self.setup['kevlar']: + for pkg_entry in [p for p in packages if self.canVerify(p)]: + self.logger.debug("Reverifying Failed Package %s" \ + % (pkg_entry.get('name'))) + states[pkg_entry] = self.VerifyPackage(pkg_entry, + self.modlists.get(pkg_entry, [])) + + for entry in [ent for ent in packages if states[ent]]: + self.modified.append(entry) + + def RemovePackages(self, packages): + """ + Remove specified entries. + + packages is a list of Package Entries with Instances generated + by FindExtraPackages(). + """ + self.logger.debug('Running Yum.RemovePackages()') + + erase_args = [] + for pkg in packages: + for inst in pkg: + nevra = build_yname(pkg.get('name'), inst) + if pkg.get('name') != 'gpg-pubkey': + self.yb.remove(**nevra) + self.modified.append(pkg) + else: + self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s-%s"\ + % (nevra['name'], nevra['version'], nevra['release'])) + self.logger.info(" This package will be deleted in a future version of the Yum driver.") + + self._runYumTransaction() + self.extra = self.FindExtraPackages() + + def VerifyPath(self, entry, _): + """Do nothing here since we only verify Path type=ignore""" + return True diff --git a/src/lib/Bcfg2/Client/Tools/YUM24.py b/src/lib/Bcfg2/Client/Tools/YUM24.py index 2bc821db3..9107d7a0d 100644 --- a/src/lib/Bcfg2/Client/Tools/YUM24.py +++ b/src/lib/Bcfg2/Client/Tools/YUM24.py @@ -5,10 +5,7 @@ import os.path import sys import yum import Bcfg2.Client.XML -import Bcfg2.Client.Tools.RPMng - -if not hasattr(Bcfg2.Client.Tools.RPMng, 'RPMng'): - raise ImportError +from Bcfg2.Client.Tools.RPM import RPM def build_yname(pkgname, inst): @@ -27,11 +24,10 @@ def build_yname(pkgname, inst): return ypname -class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): +class YUM24(RPM): """Support for Yum packages.""" pkgtype = 'yum' - - name = 'YUM24' + deprecated = True __execs__ = ['/usr/bin/yum', '/var/lib/rpm'] __handles__ = [('Package', 'yum'), ('Package', 'rpm'), @@ -57,7 +53,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): 'Instance': ['version', 'release']} def __init__(self, logger, setup, config): - Bcfg2.Client.Tools.RPMng.RPMng.__init__(self, logger, setup, config) + RPM.__init__(self, logger, setup, config) self.__important__ = self.__important__ + \ [entry.get('name') for struct in config \ for entry in struct \ @@ -140,15 +136,15 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): if len(pkgDict) > 1: # What do we do with multiple packages? - s = "YUMng: returnPackagesByDep(%s) returned many packages" + s = "YUM24: returnPackagesByDep(%s) returned many packages" self.logger.info(s % entry.get('name')) - s = "YUMng: matching packages: %s" + s = "YUM24: matching packages: %s" self.logger.info(s % str(list(pkgDict.keys()))) pkgs = set(pkgDict.keys()) & set(self.yum_installed.keys()) if len(pkgs) > 0: # Virtual packages matches an installed real package pkg = pkgDict[pkgs.pop()] - s = "YUMng: chosing: %s" % pkg.name + s = "YUM24: chosing: %s" % pkg.name self.logger.info(s) else: # What's the right package? This will fail verify @@ -157,21 +153,20 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): elif len(pkgDict) == 1: pkg = list(pkgDict.values())[0] else: # len(pkgDict) == 0 - s = "YUMng: returnPackagesByDep(%s) returned no results" + s = "YUM24: returnPackagesByDep(%s) returned no results" self.logger.info(s % entry.get('name')) pkg = None if pkg is not None: - s = "YUMng: remapping virtual package %s to %s" + s = "YUM24: remapping virtual package %s to %s" self.logger.info(s % (entry.get('name'), pkg.name)) entry.set('name', pkg.name) - return Bcfg2.Client.Tools.RPMng.RPMng.VerifyPackage(self, entry, - modlist) + return RPM.VerifyPackage(self, entry, modlist) def Install(self, packages, states): """ - Try and fix everything that RPMng.VerifyPackages() found wrong for + Try and fix everything that YUM24.VerifyPackages() found wrong for each Package Entry. This can result in individual RPMs being installed (for the first time), deleted, downgraded or upgraded. @@ -191,7 +186,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): entry is set to True. """ - self.logger.info('Running YUMng.Install()') + self.logger.info('Running YUM24.Install()') install_pkgs = [] gpg_keys = [] @@ -344,7 +339,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): packages is a list of Package Entries with Instances generated by FindExtraPackages(). """ - self.logger.debug('Running YUMng.RemovePackages()') + self.logger.debug('Running YUM24.RemovePackages()') if self.autodep: pkgtool = "/usr/bin/yum -d0 -y erase %s" @@ -368,7 +363,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): 'release': inst.get('release')} self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ % (pkgspec.get('name'), self.str_evra(pkgspec))) - self.logger.info(" This package will be deleted in a future version of the RPMng driver.") + self.logger.info(" This package will be deleted in a future version of the YUM24 driver.") cmdrc, output = self.cmd.run(pkgtool % " ".join(erase_args)) if cmdrc == 0: @@ -392,7 +387,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng): else: self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s"\ % (pkg.get('name'), self.str_evra(pkg))) - self.logger.info(" This package will be deleted in a future version of the RPMng driver.") + self.logger.info(" This package will be deleted in a future version of the YUM24 driver.") continue cmdrc, output = self.cmd.run(self.pkgtool % pkg_arg) diff --git a/src/lib/Bcfg2/Client/Tools/YUMng.py b/src/lib/Bcfg2/Client/Tools/YUMng.py index a93a48f9a..22fbba537 100644 --- a/src/lib/Bcfg2/Client/Tools/YUMng.py +++ b/src/lib/Bcfg2/Client/Tools/YUMng.py @@ -1,952 +1,9 @@ -"""This provides bcfg2 support for yum.""" +""" YUM driver called 'YUMng' for backwards compat """ -import copy -import os.path -import sys -import yum -import yum.packages -import yum.rpmtrans -import yum.callbacks -import yum.Errors -import yum.misc -import rpmUtils.arch -import Bcfg2.Client.XML -import Bcfg2.Client.Tools +from Bcfg2.Client.Tools.YUM import YUM -def build_yname(pkgname, inst): - """Build yum appropriate package name.""" - d = {} - if isinstance(inst, yum.packages.PackageObject): - for i in ['name', 'epoch', 'version', 'release', 'arch']: - d[i] = getattr(inst, i) - else: - d['name'] = pkgname - if inst.get('version') != 'any': - d['version'] = inst.get('version') - if inst.get('epoch', False): - d['epoch'] = inst.get('epoch') - if inst.get('release', False) and inst.get('release') != 'any': - d['release'] = inst.get('release') - if inst.get('arch', False) and inst.get('arch') != 'any': - d['arch'] = inst.get('arch') - return d - -def short_yname(nevra): - d = nevra.copy() - if 'version' in d: - d['ver'] = d['version'] - del d['version'] - if 'release' in d: - d['rel'] = d['release'] - del d['release'] - return d - - -def nevraString(p): - if isinstance(p, yum.packages.PackageObject): - return str(p) - else: - ret = "" - for i, j in [('epoch', '%s:'), ('name', '%s'), ('version', '-%s'), - ('release', '-%s'), ('arch', '.%s')]: - if i in p: - ret = "%s%s" % (ret, j % p[i]) - return ret - - -class RPMDisplay(yum.rpmtrans.RPMBaseCallback): - """We subclass the default RPM transaction callback so that we - can control Yum's verbosity and pipe it through the right logger.""" - - def __init__(self, logger): - yum.rpmtrans.RPMBaseCallback.__init__(self) - self.logger = logger - self.state = None - self.package = None - - def event(self, package, action, te_current, te_total, - ts_current, ts_total): - """ - @param package: A yum package object or simple string of a package name - @param action: A yum.constant transaction set state or in the obscure - rpm repackage case it could be the string 'repackaging' - @param te_current: Current number of bytes processed in the transaction - element being processed - @param te_total: Total number of bytes in the transaction element being - processed - @param ts_current: number of processes completed in whole transaction - @param ts_total: total number of processes in the transaction. - """ - - if self.package != str(package) or action != self.state: - msg = "%s: %s" % (self.action[action], package) - self.logger.info(msg) - self.state = action - self.package = str(package) - - def scriptout(self, package, msgs): - """Handle output from package scripts.""" - - if msgs: - msg = "%s: %s" % (package, msgs) - self.logger.debug(msg) - - def errorlog(self, msg): - """Deal with error reporting.""" - self.logger.error(msg) - - -class YumDisplay(yum.callbacks.ProcessTransBaseCallback): - """Class to handle display of what step we are in the Yum transaction - such as downloading packages, etc.""" - - def __init__(self, logger): - self.logger = logger - - -class YUMng(Bcfg2.Client.Tools.PkgTool): - """Support for Yum packages.""" - pkgtype = 'yum' - - name = 'YUMng' - __execs__ = [] - __handles__ = [('Package', 'yum'), - ('Package', 'rpm'), - ('Path', 'ignore')] - - __req__ = {'Package': ['name'], - 'Path': ['type']} - __ireq__ = {'Package': ['name']} - - conflicts = ['YUM24', 'RPMng'] - - def __init__(self, logger, setup, config): - self._loadYumBase(setup=setup, logger=logger) - Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config) - self.ignores = [entry.get('name') for struct in config \ - for entry in struct \ - if entry.tag == 'Path' and \ - entry.get('type') == 'ignore'] - self.instance_status = {} - self.extra_instances = [] - self.modlists = {} - self._loadConfig() - self.__important__ = self.__important__ + \ - [entry.get('name') for struct in config \ - for entry in struct \ - if entry.tag == 'Path' and \ - (entry.get('name').startswith('/etc/yum.d') \ - or entry.get('name').startswith('/etc/yum.repos.d')) \ - or entry.get('name') == '/etc/yum.conf'] - self.yum_avail = dict() - self.yum_installed = dict() - - yup = self.yb.doPackageLists(pkgnarrow='updates') - if hasattr(self.yb.rpmdb, 'pkglist'): - yinst = self.yb.rpmdb.pkglist - else: - yinst = self.yb.rpmdb.getPkgList() - for dest, source in [(self.yum_avail, yup.updates), - (self.yum_installed, yinst)]: - for pkg in source: - if dest is self.yum_avail: - pname = pkg.name - data = [(pkg.arch, (pkg.epoch, pkg.version, pkg.release))] - else: - pname = pkg[0] - data = [(pkg[1], (pkg[2], pkg[3], pkg[4]))] - if pname in dest: - dest[pname].update(data) - else: - dest[pname] = dict(data) - - def _loadYumBase(self, setup=None, logger=None): - ''' this may be called before PkgTool.__init__() is called on - this object (when the YUMng object is first instantiated; - PkgTool.__init__() calls RefreshPackages(), which requires a - YumBase object already exist), or after __init__() has - completed, when we reload the yum config before installing - packages. Consequently, we support both methods by allowing - setup and logger, the only object properties we use in this - function, to be passed as keyword arguments or to be omitted - and drawn from the object itself.''' - self.yb = yum.YumBase() - - if setup is None: - setup = self.setup - if logger is None: - logger = self.logger - - if setup['debug']: - debuglevel = 3 - elif setup['verbose']: - debuglevel = 2 - else: - debuglevel = 0 - - # pylint: disable=E1121 - try: - self.yb.preconf.debuglevel = debuglevel - self.yb._getConfig() - except AttributeError: - self.yb._getConfig(self.yb.conf.config_file_path, - debuglevel=debuglevel) - # pylint: enable=E1121 - - try: - self.yb.doConfigSetup() - self.yb.doTsSetup() - self.yb.doRpmDBSetup() - except yum.Errors.RepoError: - err = sys.exc_info()[1] - logger.error("YUMng Repository error: %s" % err) - raise Bcfg2.Client.Tools.toolInstantiationError - except Exception: - err = sys.exc_info()[1] - logger.error("YUMng error: %s" % err) - raise Bcfg2.Client.Tools.toolInstantiationError - - def _loadConfig(self): - # Process the YUMng section from the config file. - # These are all boolean flags, either we do stuff or we don't - self.pkg_checks = self.setup["yumng_pkg_checks"] - self.pkg_verify = self.setup["yumng_pkg_verify"] - self.doInstall = self.setup["yumng_installed_action"] == "install" - self.doUpgrade = self.setup["yumng_version_fail_action"] == "upgrade" - self.doReinst = self.setup["yumng_verify_fail_action"] == "reinstall" - self.verifyFlags = self.setup["yumng_verify_flags"] - - self.installOnlyPkgs = self.yb.conf.installonlypkgs - if 'gpg-pubkey' not in self.installOnlyPkgs: - self.installOnlyPkgs.append('gpg-pubkey') - - self.logger.debug("YUMng: Install missing: %s" % self.doInstall) - self.logger.debug("YUMng: pkg_checks: %s" % self.pkg_checks) - self.logger.debug("YUMng: pkg_verify: %s" % self.pkg_verify) - self.logger.debug("YUMng: Upgrade on version fail: %s" % self.doUpgrade) - self.logger.debug("YUMng: Reinstall on verify fail: %s" % self.doReinst) - self.logger.debug("YUMng: installOnlyPkgs: %s" % self.installOnlyPkgs) - self.logger.debug("YUMng: verify_flags: %s" % self.verifyFlags) - - def _fixAutoVersion(self, entry): - # old style entry; synthesize Instances from current installed - if entry.get('name') not in self.yum_installed and \ - entry.get('name') not in self.yum_avail: - # new entry; fall back to default - entry.set('version', 'any') - else: - data = copy.copy(self.yum_installed[entry.get('name')]) - if entry.get('name') in self.yum_avail: - # installed but out of date - data.update(self.yum_avail[entry.get('name')]) - for (arch, (epoch, vers, rel)) in list(data.items()): - x = Bcfg2.Client.XML.SubElement(entry, "Instance", - name=entry.get('name'), - version=vers, arch=arch, - release=rel, epoch=epoch) - if 'verify_flags' in entry.attrib: - x.set('verify_flags', entry.get('verify_flags')) - if 'verify' in entry.attrib: - x.set('verify', entry.get('verify')) - - def _buildInstances(self, entry): - instances = [inst for inst in entry \ - if inst.tag == 'Instance' or inst.tag == 'Package'] - - # XXX: Uniquify instances. Cases where duplicates are returned. - # However, the elements aren't comparable. - - if instances == []: - # We have an old style no Instance entry. Convert it to new style. - instance = Bcfg2.Client.XML.SubElement(entry, 'Package') - for attrib in list(entry.attrib.keys()): - instance.attrib[attrib] = entry.attrib[attrib] - instances = [instance] - - return instances - - def _getGPGKeysAsPackages(self): - """Return a list of the GPG RPM signing keys installed on the - system as a list of Package Objects.""" - - # XXX GPG keys existing in the RPMDB have numbered days - # and newer Yum versions will not return information about them - if hasattr(self.yb.rpmdb, 'returnGPGPubkeyPackages'): - return self.yb.rpmdb.returnGPGPubkeyPackages() - return self.yb.rpmdb.searchNevra(name='gpg-pubkey') - - def _verifyHelper(self, po): - # This code primarly deals with a yum bug where the PO.verify() - # method does not properly take into count multilib sharing of files. - # Neither does RPM proper, really....it just ignores the problem. - def verify(p): - # disabling file checksums is a new feature yum 3.2.17-ish - try: - vResult = p.verify(fast=self.setup.get('quick', False)) - except TypeError: - # Older Yum API - vResult = p.verify() - return vResult - - key = (po.name, po.epoch, po.version, po.release, po.arch) - if key in self.verifyCache: - results = self.verifyCache[key] - else: - results = verify(po) - self.verifyCache[key] = results - if not rpmUtils.arch.isMultiLibArch(): - return results - - # Okay deal with a buggy yum multilib and verify - packages = self.yb.rpmdb.searchNevra(name=po.name, epoch=po.epoch, - ver=po.version, rel=po.release) # find all arches of pkg - if len(packages) == 1: - return results # No mathcing multilib packages - - files = set(po.returnFileEntries()) # Will be the list of common fns - common = {} - for p in packages: - if p != po: - files = files & set(p.returnFileEntries()) - for p in packages: - k = (p.name, p.epoch, p.version, p.release, p.arch) - self.logger.debug("Multilib Verify: comparing %s to %s" \ - % (po, p)) - if k in self.verifyCache: - v = self.verifyCache[k] - else: - v = verify(p) - self.verifyCache[k] = v - - for fn, probs in list(v.items()): - # file problems must exist in ALL multilib packages to be real - if fn in files: - common[fn] = common.get(fn, 0) + 1 - - flag = len(packages) - 1 - for fn, i in list(common.items()): - if i == flag: - # this fn had verify problems in all but one of the multilib - # packages. That means its correct in the package that's - # "on top." Therefore, this is a fake verify problem. - if fn in results: - del results[fn] - - return results - - def RefreshPackages(self): - """ - Creates self.installed{} which is a dict of installed packages. - - The dict items are lists of nevra dicts. This loosely matches the - config from the server and what rpmtools uses to specify pacakges. - - e.g. - - self.installed['foo'] = [ {'name':'foo', 'epoch':None, - 'version':'1', 'release':2, - 'arch':'i386'}, - {'name':'foo', 'epoch':None, - 'version':'1', 'release':2, - 'arch':'x86_64'} ] - """ - - self.installed = {} - packages = self._getGPGKeysAsPackages() + \ - self.yb.rpmdb.returnPackages() - for po in packages: - d = {} - for i in ['name', 'epoch', 'version', 'release', 'arch']: - if i == 'arch' and getattr(po, i) is None: - d[i] = 'noarch' - elif i == 'epoch' and getattr(po, i) is None: - d[i] = '0' - else: - d[i] = getattr(po, i) - self.installed.setdefault(po.name, []).append(d) - - def VerifyPackage(self, entry, modlist, pinned_version=None): - """ - Verify Package status for entry. - Performs the following: - - Checks for the presence of required Package Instances. - - Compares the evra 'version' info against self.installed{}. - - RPM level package verify (rpm --verify). - - Checks for the presence of unrequired package instances. - - Produces the following dict and list for YUMng.Install() to use: - For installs/upgrades/fixes of required instances: - instance_status = { <Instance Element Object>: - { 'installed': True|False, - 'version_fail': True|False, - 'verify_fail': True|False, - 'pkg': <Package Element Object>, - 'modlist': [ <filename>, ... ], - 'verify' : [ <rpm --verify results> ] - }, ...... - } - - For deletions of unrequired instances: - extra_instances = [ <Package Element Object>, ..... ] - - Constructs the text prompts for interactive mode. - """ - - if entry.get('version', False) == 'auto': - self._fixAutoVersion(entry) - - self.logger.debug("Verifying package instances for %s" % - entry.get('name')) - - self.verifyCache = {} # Used for checking multilib packages - self.modlists[entry] = modlist - instances = self._buildInstances(entry) - packageCache = [] - package_fail = False - qtext_versions = [] - virtPkg = False - pkg_checks = self.pkg_checks and \ - entry.get('pkg_checks', 'true').lower() == 'true' - pkg_verify = self.pkg_verify and \ - entry.get('pkg_verify', 'true').lower() == 'true' - - if entry.get('name') == 'gpg-pubkey': - POs = self._getGPGKeysAsPackages() - pkg_verify = False # No files here to verify - else: - POs = self.yb.rpmdb.searchNevra(name=entry.get('name')) - if len(POs) == 0: - # Some sort of virtual capability? Try to resolve it - POs = self.yb.rpmdb.searchProvides(entry.get('name')) - if len(POs) > 0: - virtPkg = True - self.logger.info("%s appears to be provided by:" % - entry.get('name')) - for p in POs: - self.logger.info(" %s" % p) - - for inst in instances: - nevra = build_yname(entry.get('name'), inst) - snevra = short_yname(nevra) - if nevra in packageCache: - continue # Ignore duplicate instances - else: - packageCache.append(nevra) - - self.logger.debug("Verifying: %s" % nevraString(nevra)) - - # Set some defaults here - stat = self.instance_status.setdefault(inst, {}) - stat['installed'] = True - stat['version_fail'] = False - stat['verify'] = {} - stat['verify_fail'] = False - stat['pkg'] = entry - stat['modlist'] = modlist - if inst.get('verify_flags'): - # this splits on either space or comma - verify_flags = \ - inst.get('verify_flags').lower().replace(' ', - ',').split(',') - else: - verify_flags = self.verifyFlags - - if 'arch' in nevra: - # If arch is specified use it to select the package - _POs = [ p for p in POs if p.arch == nevra['arch'] ] - else: - _POs = POs - if len(_POs) == 0: - # Package (name, arch) not installed - entry.set('current_exists', 'false') - self.logger.debug(" %s is not installed" % nevraString(nevra)) - stat['installed'] = False - package_fail = True - qtext_versions.append("I(%s)" % nevra) - continue - - if not pkg_checks: - continue - - # Check EVR - if virtPkg: - # we need to make sure that the version of the symbol - # provided matches the one required in the - # configuration - vlist = [] - for attr in ["epoch", "version", "release"]: - vlist.append(nevra.get(attr)) - if tuple(vlist) == (None, None, None): - # we just require the package name, no particular - # version, so just make a copy of POs since every - # package that provides this symbol satisfies the - # requirement - _POs = [po for po in POs] - else: - _POs = [po for po in POs - if po.checkPrco('provides', - (nevra["name"], 'EQ', - tuple(vlist)))] - elif entry.get('name') == 'gpg-pubkey': - if 'version' not in nevra: - m = "Skipping verify: gpg-pubkey without an RPM version." - self.logger.warning(m) - continue - if 'release' not in nevra: - m = "Skipping verify: gpg-pubkey without an RPM release." - self.logger.warning(m) - continue - _POs = [p for p in POs if p.version == nevra['version'] \ - and p.release == nevra['release']] - else: - _POs = self.yb.rpmdb.searchNevra(**snevra) - if len(_POs) == 0: - package_fail = True - stat['version_fail'] = True - # Just chose the first pkg for the error message - if virtPkg: - provTuple = \ - [p for p in POs[0].provides - if p[0] == entry.get("name")][0] - entry.set('current_version', "%s:%s-%s" % provTuple[2]) - self.logger.info(" %s: Wrong version installed. " - "Want %s, but %s provides %s" % - (entry.get("name"), - nevraString(nevra), - nevraString(POs[0]), - yum.misc.prco_tuple_to_string(provTuple))) - else: - entry.set('current_version', "%s:%s-%s.%s" % - (POs[0].epoch, - POs[0].version, - POs[0].release, - POs[0].arch)) - self.logger.info(" %s: Wrong version installed. " - "Want %s, but have %s" % - (entry.get("name"), - nevraString(nevra), - nevraString(POs[0]))) - entry.set('version', "%s:%s-%s.%s" % - (nevra.get('epoch', 'any'), - nevra.get('version', 'any'), - nevra.get('release', 'any'), - nevra.get('arch', 'any'))) - qtext_versions.append("U(%s)" % str(POs[0])) - continue - - if self.setup.get('quick', False): - # Passed -q on the command line - continue - if not (pkg_verify and \ - inst.get('pkg_verify', 'true').lower() == 'true'): - continue - - # XXX: We ignore GPG sig checking the package as it - # has nothing to do with the individual file hash/size/etc. - # GPG checking the package only eaxmines some header/rpmdb - # wacky-ness, and will not properly detect a compromised rpmdb. - # Yum's verify routine does not support it for that reaosn. - - if len(_POs) > 1: - self.logger.debug(" Verify Instance found many packages:") - for po in _POs: - self.logger.debug(" %s" % str(po)) - - try: - vResult = self._verifyHelper(_POs[0]) - except Exception: - e = sys.exc_info()[1] - # Unknown Yum exception - self.logger.warning(" Verify Exception: %s" % str(e)) - package_fail = True - continue - - # Now take out the Yum specific objects / modlists / unproblems - ignores = [ig.get('name') for ig in entry.findall('Ignore')] + \ - [ig.get('name') for ig in inst.findall('Ignore')] + \ - self.ignores - for fn, probs in list(vResult.items()): - if fn in modlist: - self.logger.debug(" %s in modlist, skipping" % fn) - continue - if fn in ignores: - self.logger.debug(" %s in ignore list, skipping" % fn) - continue - tmp = [] - for p in probs: - if p.type == 'missing' and os.path.islink(fn): - continue - elif 'no' + p.type in verify_flags: - continue - if p.type not in ['missingok', 'ghost']: - tmp.append((p.type, p.message)) - if tmp != []: - stat['verify'][fn] = tmp - - if stat['verify'] != {}: - stat['verify_fail'] = True - package_fail = True - self.logger.debug("It is suggested that you either manage " - "these files, revert the changes, or ignore " - "false failures:") - self.logger.debug(" Verify Problems:") - for fn, probs in list(stat['verify'].items()): - self.logger.debug(" %s" % fn) - for p in probs: - self.logger.debug(" %s: %s" % p) - - if len(POs) > 0: - # Is this an install only package? We just look at the first one - provides = set([p[0] for p in POs[0].provides] + [POs[0].name]) - install_only = len(set(self.installOnlyPkgs) & provides) > 0 - else: - install_only = False - - if virtPkg or (install_only and not self.setup['kevlar']): - # XXX: virtual capability supplied, we a probably dealing - # with multiple packages of different names. This check - # doesn't make a lot of since in this case - # XXX: install_only: Yum may clean some of these up itself. - # Otherwise having multiple instances of install only packages - # is considered correct - self.extra_instances = None - else: - self.extra_instances = self.FindExtraInstances(entry, POs) - if self.extra_instances is not None: - package_fail = True - - return not package_fail - - def FindExtraInstances(self, entry, POs): - """ - Check for installed instances that are not in the config. - Return a Package Entry with Instances to remove, or None if there - are no Instances to remove. - - """ - if len(POs) == 0: - return None - name = entry.get('name') - extra_entry = Bcfg2.Client.XML.Element('Package', name=name, - type=self.pkgtype) - instances = self._buildInstances(entry) - _POs = [p for p in POs] # Shallow copy - - # Algorythm is sensitive to duplicates, check for them - checked = [] - for inst in instances: - nevra = build_yname(name, inst) - snevra = short_yname(nevra) - pkgs = self.yb.rpmdb.searchNevra(**snevra) - flag = True - if len(pkgs) > 0: - if pkgs[0] in checked: - continue # We've already taken care of this Instance - else: - checked.append(pkgs[0]) - _POs.remove(pkgs[0]) - - for p in _POs: - self.logger.debug(" Extra Instance Found: %s" % str(p)) - Bcfg2.Client.XML.SubElement(extra_entry, 'Instance', - epoch=p.epoch, name=p.name, version=p.version, - release=p.release, arch=p.arch) - - if _POs == []: - return None - else: - return extra_entry - - def FindExtraPackages(self): - """Find extra packages.""" - packages = [e.get('name') for e in self.getSupportedEntries()] - extras = [] - - for p in list(self.installed.keys()): - if p not in packages: - entry = Bcfg2.Client.XML.Element('Package', name=p, - type=self.pkgtype) - for i in self.installed[p]: - inst = Bcfg2.Client.XML.SubElement(entry, - 'Instance', - epoch=i['epoch'], - version=i['version'], - release=i['release'], - arch=i['arch']) - - extras.append(entry) - - return extras - - def _installGPGKey(self, inst, key_file): - """Examine the GPG keys carefully before installation. Avoid - installing duplicate keys. Returns True on successful install.""" - - # RPM Transaction Set - ts = self.yb.rpmdb.readOnlyTS() - - if not os.path.exists(key_file): - self.logger.debug("GPG Key file %s not installed" % key_file) - return False - - rawkey = open(key_file).read() - gpg = yum.misc.getgpgkeyinfo(rawkey) - - ver = yum.misc.keyIdToRPMVer(gpg['keyid']) - rel = yum.misc.keyIdToRPMVer(gpg['timestamp']) - if not (ver == inst.get('version') and rel == inst.get('release')): - self.logger.info("GPG key file %s does not match gpg-pubkey-%s-%s"\ - % (key_file, inst.get('version'), - inst.get('release'))) - return False - - if not yum.misc.keyInstalled(ts, gpg['keyid'], - gpg['timestamp']) == 0: - result = ts.pgpImportPubkey(yum.misc.procgpgkey(rawkey)) - else: - self.logger.debug("gpg-pubkey-%s-%s already installed"\ - % (inst.get('version'), - inst.get('release'))) - return True - - if result != 0: - self.logger.debug("Unable to install %s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), - nevraString(inst))) - return False - else: - self.logger.debug("Installed %s-%s-%s" % \ - (self.instance_status[inst].get('pkg').get('name'), - inst.get('version'), inst.get('release'))) - return True - - def _runYumTransaction(self): - def cleanup(): - self.yb.closeRpmDB() - self.RefreshPackages() - - rDisplay = RPMDisplay(self.logger) - yDisplay = YumDisplay(self.logger) - # Run the Yum Transaction - try: - rescode, restring = self.yb.buildTransaction() - except yum.Errors.YumBaseError: - e = sys.exc_info()[1] - self.logger.error("Yum transaction error: %s" % str(e)) - cleanup() - return - - self.logger.debug("Initial Yum buildTransaction() run said:") - self.logger.debug(" resultcode: %s, msgs: %s" \ - % (rescode, restring)) - - if rescode != 1: - # Transaction built successfully, run it - try: - self.yb.processTransaction(callback=yDisplay, - rpmDisplay=rDisplay) - self.logger.info("Single Pass for Install Succeeded") - except yum.Errors.YumBaseError: - e = sys.exc_info()[1] - self.logger.error("Yum transaction error: %s" % str(e)) - cleanup() - return - else: - # The yum command failed. No packages installed. - # Try installing instances individually. - self.logger.error("Single Pass Install of Packages Failed") - skipBroken = self.yb.conf.skip_broken - self.yb.conf.skip_broken = True - try: - rescode, restring = self.yb.buildTransaction() - if rescode != 1: - self.yb.processTransaction(callback=yDisplay, - rpmDisplay=rDisplay) - self.logger.debug( - "Second pass install did not install all packages") - else: - self.logger.error("Second pass yum install failed.") - self.logger.debug(" %s" % restring) - except yum.Errors.YumBaseError: - e = sys.exc_info()[1] - self.logger.error("Yum transaction error: %s" % str(e)) - - self.yb.conf.skip_broken = skipBroken - - cleanup() - - def Install(self, packages, states): - """ - Try and fix everything that YUMng.VerifyPackages() found wrong for - each Package Entry. This can result in individual RPMs being - installed (for the first time), deleted, downgraded - or upgraded. - - packages is a list of Package Elements that has - states[<Package Element>] == False - - The following effects occur: - - states{} is conditionally updated for each package. - - self.installed{} is rebuilt, possibly multiple times. - - self.instance_status{} is conditionally updated for each instance - of a package. - - Each package will be added to self.modified[] if its states{} - entry is set to True. - - """ - self.logger.debug('Running YUMng.Install()') - - install_pkgs = [] - gpg_keys = [] - upgrade_pkgs = [] - reinstall_pkgs = [] - - def queuePkg(pkg, inst, queue): - if pkg.get('name') == 'gpg-pubkey': - gpg_keys.append(inst) - else: - queue.append(inst) - - # Remove extra instances. - # Can not reverify because we don't have a package entry. - if self.extra_instances is not None and len(self.extra_instances) > 0: - if (self.setup.get('remove') == 'all' or \ - self.setup.get('remove') == 'packages'): - self.RemovePackages(self.extra_instances) - else: - self.logger.info("The following extra package instances will be removed by the '-r' option:") - for pkg in self.extra_instances: - for inst in pkg: - self.logger.info(" %s %s" % \ - ((pkg.get('name'), nevraString(inst)))) - - # Figure out which instances of the packages actually need something - # doing to them and place in the appropriate work 'queue'. - for pkg in packages: - insts = [pinst for pinst in pkg \ - if pinst.tag in ['Instance', 'Package']] - if insts: - for inst in insts: - if inst not in self.instance_status: - m = " Asked to install/update package never verified" - p = nevraString(build_yname(pkg.get('name'), inst)) - self.logger.warning("%s: %s" % (m, p)) - continue - status = self.instance_status[inst] - if not status.get('installed', False) and self.doInstall: - queuePkg(pkg, inst, install_pkgs) - elif status.get('version_fail', False) and self.doUpgrade: - queuePkg(pkg, inst, upgrade_pkgs) - elif status.get('verify_fail', False) and self.doReinst: - queuePkg(pkg, inst, reinstall_pkgs) - else: - # Either there was no Install/Version/Verify - # task to be done or the user disabled the actions - # in the configuration. XXX Logging for the latter? - pass - else: - msg = "YUMng: Package tag found where Instance expected: %s" - self.logger.warning(msg % pkg.get('name')) - queuePkg(pkg, pkg, install_pkgs) - - # Install GPG keys. - # Alternatively specify the required keys using 'gpgkey' in the - # repository definition in yum.conf. YUM will install the keys - # automatically. - if len(gpg_keys) > 0: - self.logger.info("Installing GPG keys.") - for inst in gpg_keys: - if inst.get('simplefile') is None: - self.logger.error("GPG key has no simplefile attribute") - continue - key_file = os.path.join(self.instance_status[inst].get('pkg').get('uri'), \ - inst.get('simplefile')) - self._installGPGKey(inst, key_file) - - self.RefreshPackages() - pkg = self.instance_status[gpg_keys[0]].get('pkg') - states[pkg] = self.VerifyPackage(pkg, []) - - # We want to reload all Yum configuration in case we've - # deployed new .repo files we should consider - self._loadYumBase() - - # Install packages. - if len(install_pkgs) > 0: - self.logger.info("Attempting to install packages") - - for inst in install_pkgs: - pkg_arg = self.instance_status[inst].get('pkg').get('name') - self.logger.debug("Installing %s" % pkg_arg) - try: - self.yb.install(**build_yname(pkg_arg, inst)) - except yum.Errors.YumBaseError: - yume = sys.exc_info()[1] - self.logger.error("Error installing package %s: %s" % - (pkg_arg, yume)) - - if len(upgrade_pkgs) > 0: - self.logger.info("Attempting to upgrade packages") - - for inst in upgrade_pkgs: - pkg_arg = self.instance_status[inst].get('pkg').get('name') - self.logger.debug("Upgrading %s" % pkg_arg) - try: - self.yb.update(**build_yname(pkg_arg, inst)) - except yum.Errors.YumBaseError: - yume = sys.exc_info()[1] - self.logger.error("Error upgrading package %s: %s" % - (pkg_arg, yume)) - - if len(reinstall_pkgs) > 0: - self.logger.info("Attempting to reinstall packages") - for inst in reinstall_pkgs: - pkg_arg = self.instance_status[inst].get('pkg').get('name') - self.logger.debug("Reinstalling %s" % pkg_arg) - try: - self.yb.reinstall(**build_yname(pkg_arg, inst)) - except yum.Errors.YumBaseError: - yume = sys.exc_info()[1] - self.logger.error("Error reinstalling package %s: %s" % - (pkg_arg, yume)) - - self._runYumTransaction() - - if not self.setup['kevlar']: - for pkg_entry in [p for p in packages if self.canVerify(p)]: - self.logger.debug("Reverifying Failed Package %s" \ - % (pkg_entry.get('name'))) - states[pkg_entry] = self.VerifyPackage(pkg_entry, - self.modlists.get(pkg_entry, [])) - - for entry in [ent for ent in packages if states[ent]]: - self.modified.append(entry) - - def RemovePackages(self, packages): - """ - Remove specified entries. - - packages is a list of Package Entries with Instances generated - by FindExtraPackages(). - """ - self.logger.debug('Running YUMng.RemovePackages()') - - erase_args = [] - for pkg in packages: - for inst in pkg: - nevra = build_yname(pkg.get('name'), inst) - if pkg.get('name') != 'gpg-pubkey': - self.yb.remove(**nevra) - self.modified.append(pkg) - else: - self.logger.info("WARNING: gpg-pubkey package not in configuration %s %s-%s"\ - % (nevra['name'], nevra['version'], nevra['release'])) - self.logger.info(" This package will be deleted in a future version of the YUMng driver.") - - self._runYumTransaction() - self.extra = self.FindExtraPackages() - - def VerifyPath(self, entry, _): - """Do nothing here since we only verify Path type=ignore""" - return True +class YUMng(YUM): + """ YUM driver called 'YUMng' for backwards compat """ + deprecated = True + conflicts = ['YUM24', 'RPM', 'RPMng'] diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index 51d3ceecf..c11b96ef7 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -42,6 +42,7 @@ class Tool(object): __handles__ = [] __req__ = {} __important__ = [] + deprecated = False def __init__(self, logger, setup, config): self.setup = setup @@ -144,7 +145,7 @@ class Tool(object): required.extend(self.__req__[entry.tag][entry.get("type")]) except KeyError: pass - + return [attr for attr in required if attr not in entry.attrib or not entry.attrib[attr]] diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py index f1bc54d49..04233f165 100644 --- a/src/lib/Bcfg2/Options.py +++ b/src/lib/Bcfg2/Options.py @@ -776,55 +776,63 @@ CLIENT_PORTAGE_BINPKGONLY = \ default=False, cf=('Portage', 'binpkgonly'), cook=get_bool) -CLIENT_RPMNG_INSTALLONLY = \ - Option('RPMng install-only packages', +CLIENT_RPM_INSTALLONLY = \ + Option('RPM install-only packages', default=['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], - cf=('RPMng', 'installonlypackages'), + cf=('RPM', 'installonlypackages'), + deprecated_cf=('RPMng', 'installonlypackages'), cook=list_split) -CLIENT_RPMNG_PKG_CHECKS = \ - Option("Perform RPMng package checks", +CLIENT_RPM_PKG_CHECKS = \ + Option("Perform RPM package checks", default=True, - cf=('RPMng', 'pkg_checks'), + cf=('RPM', 'pkg_checks'), + deprecated_cf=('RPMng', 'pkg_checks'), cook=get_bool) -CLIENT_RPMNG_PKG_VERIFY = \ - Option("Perform RPMng package verify", +CLIENT_RPM_PKG_VERIFY = \ + Option("Perform RPM package verify", default=True, - cf=('RPMng', 'pkg_verify'), + cf=('RPM', 'pkg_verify'), + deprecated_cf=('RPMng', 'pkg_verify'), cook=get_bool) -CLIENT_RPMNG_INSTALLED_ACTION = \ - Option("RPMng installed action", +CLIENT_RPM_INSTALLED_ACTION = \ + Option("RPM installed action", default="install", - cf=('RPMng', 'installed_action')) -CLIENT_RPMNG_ERASE_FLAGS = \ - Option("RPMng erase flags", + cf=('RPM', 'installed_action'), + deprecated_cf=('RPMng', 'installed_action')) +CLIENT_RPM_ERASE_FLAGS = \ + Option("RPM erase flags", default=["allmatches"], - cf=('RPMng', 'erase_flags'), + cf=('RPM', 'erase_flags'), + deprecated_cf=('RPMng', 'erase_flags'), cook=list_split) -CLIENT_RPMNG_VERSION_FAIL_ACTION = \ - Option("RPMng version fail action", +CLIENT_RPM_VERSION_FAIL_ACTION = \ + Option("RPM version fail action", default="upgrade", - cf=('RPMng', 'version_fail_action')) -CLIENT_RPMNG_VERIFY_FAIL_ACTION = \ - Option("RPMng verify fail action", + cf=('RPM', 'version_fail_action'), + deprecated_cf=('RPMng', 'version_fail_action')) +CLIENT_RPM_VERIFY_FAIL_ACTION = \ + Option("RPM verify fail action", default="reinstall", - cf=('RPMng', 'verify_fail_action')) -CLIENT_RPMNG_VERIFY_FLAGS = \ - Option("RPMng verify flags", + cf=('RPM', 'verify_fail_action'), + deprecated_cf=('RPMng', 'verify_fail_action')) +CLIENT_RPM_VERIFY_FLAGS = \ + Option("RPM verify flags", default=[], - cf=('RPMng', 'verify_flags'), + cf=('RPM', 'verify_flags'), + deprecated_cf=('RPMng', 'verify_flags'), cook=list_split) CLIENT_YUM24_INSTALLONLY = \ - Option('RPMng install-only packages', + Option('YUM24 install-only packages', default=['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp', 'kernel-modules', 'kernel-debug', 'kernel-unsupported', 'kernel-devel', 'kernel-source', 'kernel-default', 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen', 'gpg-pubkey'], - cf=('RPMng', 'installonlypackages'), + cf=('YUM24', 'installonlypackages'), cook=list_split) CLIENT_YUM24_PKG_CHECKS = \ Option("Perform YUM24 package checks", @@ -863,32 +871,38 @@ CLIENT_YUM24_AUTODEP = \ default=True, cf=('YUM24', 'autodep'), cook=get_bool) -CLIENT_YUMNG_PKG_CHECKS = \ - Option("Perform YUMng package checks", +CLIENT_YUM_PKG_CHECKS = \ + Option("Perform YUM package checks", default=True, - cf=('YUMng', 'pkg_checks'), + cf=('YUM', 'pkg_checks'), + deprecated_cf=('YUMng', 'pkg_checks'), cook=get_bool) -CLIENT_YUMNG_PKG_VERIFY = \ - Option("Perform YUMng package verify", +CLIENT_YUM_PKG_VERIFY = \ + Option("Perform YUM package verify", default=True, - cf=('YUMng', 'pkg_verify'), + cf=('YUM', 'pkg_verify'), + deprecated_cf=('YUMng', 'pkg_verify'), cook=get_bool) -CLIENT_YUMNG_INSTALLED_ACTION = \ - Option("YUMng installed action", +CLIENT_YUM_INSTALLED_ACTION = \ + Option("YUM installed action", default="install", - cf=('YUMng', 'installed_action')) -CLIENT_YUMNG_VERSION_FAIL_ACTION = \ - Option("YUMng version fail action", + cf=('YUM', 'installed_action'), + deprecated_cf=('YUMng', 'installed_action')) +CLIENT_YUM_VERSION_FAIL_ACTION = \ + Option("YUM version fail action", default="upgrade", - cf=('YUMng', 'version_fail_action')) -CLIENT_YUMNG_VERIFY_FAIL_ACTION = \ - Option("YUMng verify fail action", + cf=('YUM', 'version_fail_action'), + deprecated_cf=('YUMng', 'version_fail_action')) +CLIENT_YUM_VERIFY_FAIL_ACTION = \ + Option("YUM verify fail action", default="reinstall", - cf=('YUMng', 'verify_fail_action')) -CLIENT_YUMNG_VERIFY_FLAGS = \ - Option("YUMng verify flags", + cf=('YUM', 'verify_fail_action'), + deprecated_cf=('YUMng', 'verify_fail_action')) +CLIENT_YUM_VERIFY_FLAGS = \ + Option("YUM verify flags", default=[], - cf=('YUMng', 'verify_flags'), + cf=('YUM', 'verify_flags'), + deprecated_cf=('YUMng', 'verify_flags'), cook=list_split) # Logging options @@ -1008,14 +1022,14 @@ DRIVER_OPTIONS = \ apt_var_path=CLIENT_APT_TOOLS_VAR_PATH, apt_etc_path=CLIENT_SYSTEM_ETC_PATH, portage_binpkgonly=CLIENT_PORTAGE_BINPKGONLY, - rpmng_installonly=CLIENT_RPMNG_INSTALLONLY, - rpmng_pkg_checks=CLIENT_RPMNG_PKG_CHECKS, - rpmng_pkg_verify=CLIENT_RPMNG_PKG_VERIFY, - rpmng_installed_action=CLIENT_RPMNG_INSTALLED_ACTION, - rpmng_erase_flags=CLIENT_RPMNG_ERASE_FLAGS, - rpmng_version_fail_action=CLIENT_RPMNG_VERSION_FAIL_ACTION, - rpmng_verify_fail_action=CLIENT_RPMNG_VERIFY_FAIL_ACTION, - rpmng_verify_flags=CLIENT_RPMNG_VERIFY_FLAGS, + rpm_installonly=CLIENT_RPM_INSTALLONLY, + rpm_pkg_checks=CLIENT_RPM_PKG_CHECKS, + rpm_pkg_verify=CLIENT_RPM_PKG_VERIFY, + rpm_installed_action=CLIENT_RPM_INSTALLED_ACTION, + rpm_erase_flags=CLIENT_RPM_ERASE_FLAGS, + rpm_version_fail_action=CLIENT_RPM_VERSION_FAIL_ACTION, + rpm_verify_fail_action=CLIENT_RPM_VERIFY_FAIL_ACTION, + rpm_verify_flags=CLIENT_RPM_VERIFY_FLAGS, yum24_installonly=CLIENT_YUM24_INSTALLONLY, yum24_pkg_checks=CLIENT_YUM24_PKG_CHECKS, yum24_pkg_verify=CLIENT_YUM24_PKG_VERIFY, @@ -1025,12 +1039,12 @@ DRIVER_OPTIONS = \ yum24_verify_fail_action=CLIENT_YUM24_VERIFY_FAIL_ACTION, yum24_verify_flags=CLIENT_YUM24_VERIFY_FLAGS, yum24_autodep=CLIENT_YUM24_AUTODEP, - yumng_pkg_checks=CLIENT_YUMNG_PKG_CHECKS, - yumng_pkg_verify=CLIENT_YUMNG_PKG_VERIFY, - yumng_installed_action=CLIENT_YUMNG_INSTALLED_ACTION, - yumng_version_fail_action=CLIENT_YUMNG_VERSION_FAIL_ACTION, - yumng_verify_fail_action=CLIENT_YUMNG_VERIFY_FAIL_ACTION, - yumng_verify_flags=CLIENT_YUMNG_VERIFY_FLAGS) + yum_pkg_checks=CLIENT_YUM_PKG_CHECKS, + yum_pkg_verify=CLIENT_YUM_PKG_VERIFY, + yum_installed_action=CLIENT_YUM_INSTALLED_ACTION, + yum_version_fail_action=CLIENT_YUM_VERSION_FAIL_ACTION, + yum_verify_fail_action=CLIENT_YUM_VERIFY_FAIL_ACTION, + yum_verify_flags=CLIENT_YUM_VERIFY_FLAGS) CLIENT_COMMON_OPTIONS = \ dict(extra=CLIENT_EXTRA_DISPLAY, |