In the course of getting CFEngine 3 package management to do what I needed, I expanded upon some very nice ideas by others in the community and ran into a few frustrations with RPM and CFEngine that I’ve been able to overcome. Wanted to take some time to contribute those back.
The examples below are written against the package_method bodies in the Yale Cfengine 3 library.
Examples
yum_rpm_byname – install a package, version-agnostically
packages:
"wireshark" package_policy => "add", package_method => yum_rpm_byname, action => actionsettings_fix_inform("inform");Notes:
With policy “update” or “addupdate”, this will auto-update clients when new versions appear in repos exposed to them. Avoid this if you care about version specificity.
yum_rpm_exact – install a package at a particular version
packages:
"wireshark" package_policy => "add", package_method => yum_rpm_exact, package_version => "1.0.15-1.el5_6.4", package_architectures => { "x86_64" }, action => actionsettings_fix_inform("inform");
Notes:
You must specify “package_version” in any promises where this body is used. Otherwise, the version gets set to “*”, which causes Cfengine to refer to the package in terms of package-* (see package_name_convention), which could match on (and install/delete, depending on package_policy) all kinds of packages unintentionally.
Cfengine expects the package_version’s format to match version-release (see “Specifying package names” in the yum manpage). All other formats will fail to properly match and yield poor results.
$ /bin/rpm -qa --qf '%{version}-%{release}\n' wireshark
1.0.15-1.el5_6.4
Note that ‘version’ and ‘release’ are distinct pieces of metadata describing a package revision (in this case, Red Hat’s 6.4 release for RHEL5 of wireshark version 1.0.15).
yum_rpm_exact_enablerepo(repo) – install a package at a particular version, against an otherwise-disabled repository
packages:
"rhm-docs" package_policy => "add", package_method => yum_rpm_exact_enablerepo("yale"), package_version => "0.7.946106-8.el5", package_architectures => { "noarch" }, action => actionsettings_fix_inform("inform");
This turns on an otherwise-disabled Yum repository temporarily for the purpose of installing a particular package. We use this to make way for per-package overrides from a local Yum repository.
Notes:
‘repo’ must already be defined in your yum configuration – otherwise yum will scream.
Caveats for yum_rpm_exact apply to this one too – see above.
yum_rpm_byname_enablerepo(repo) – like yum_rpm_byname, but with repository
“repo” enabled
packages:
"wireshark" package_policy => "add", package_method => yum_rpm_byname_enablerepo("yale-testing"), action => actionsettings_fix_inform("inform");
Notes:
Repo must already be defined in a file in yum.repos.d – otherwise yum will scream.
Caveats for yum_rpm_byname apply to this one too – see above.
Gotchas
Version comparator problems
As of 3.1.2, there’s a bug in Cfengine’s logic for comparing installed and requested package versions; it is unable to ever detect that the correct version is installed and will attempt to update in perpetuity. This is supposed to be fixed in later versions (>=3.2.0 RC); I haven’t tested newer versions myself but use a workaround for agents running versions affected by this bug.
packages:
# First we want wireshark 1.0.11
"wireshark" package_policy => "add", package_method => yum_rpm_exact, package_version => "1.0.11-1.el5_6.4", package_architectures => { "x86_64" }, action => actionsettings_fix_inform("inform");
# Later we want wireshark 1.0.15. Swap above policy for:
"wireshark" package_policy => "addupdate", package_method => yum_rpm_exact, package_version => "1.0.15-1.el5_6.4", package_architectures => { "x86_64" }, action => actionsettings_fix_inform("inform");Side effect of workaround: CFEngine will attempt to “update” wireshark to 1.0.15 on every agent run, until the cows come home.
On alternative methods of version formatting, like epochs
Some packages’ versions may be odd in some way that makes referring to them by “version-release” unworkable. In that case we would need to add a new package_method for them in library.cf. We decided to break from our usual approach for library design and not create a full-featured package_method body, because it would have required passing several long, gory regular expressions as parameters.
The most likely case of need for a different version format is the addition
of epochs [1]. Per Red Hat, no two packages release by them have the same “version-release” string with different epochs, so epochs are spurious for anything that comes from them. This may *not* be the case for EPEL, 3rd party RPMs, etc.
Originally, I hoped to refer to version numbers in terms of “epoch:version-release” in our packages promises and underlying package_method bodies, to facilitate CFEngine’s comparison of version numbers (which, remember, must be a universal algorithm, since Cfengine treats package managers as black boxes). Unfortunately, the means of passing versions by Cfengine promises and yum formatting requirements make this a pain. Per the yum manpage, any formats that includes the epoch also require architecture:
Specifying package names A package can be referred to for install,update,list,remove etc with any of the following:
name name.arch name-ver name-ver-rel name-ver-rel.arch name-epoch:ver-rel.arch epoch:name-ver-rel.archBecause of the mutually-exclusive two means of specifying version and architecture for a package (see below), we’d have to pass
“name-epoch:ver-rel.arch” as the promiser and craft regexes for drawing out
the name, version (in this case, by format “epoch:ver-rel”) and arch out
of the promiser to pass to Cfengine. This gets seriously icky because of
inconsistent naming of packages and versions.
Cfengine uses complex logic for comparing version formats, so any diversion from “ver-rel” as the formatting scheme should be well-tested; this is the widely-used format in the forum and bug tracker, so there’s also more community weight behind this as opposed to “ver” or “epoch:ver-rel”.
Two methods of specifying intended version/architecture
We pass the package version as the promiser and add package_version and
package_architectures as promises attributes.
It is also possible to define regular expressions for drawing n,v,a (name, version and architecture) out of the (more detailed) promiser, as an alternative to passing package_version and package_architectures explicitly. I think the regexes get dorky for this, so I avoid it.
If one should decide to do this, know that the two approaches to divining version and architecture are mutually exclusive within package_method (that is, don’t try to pull n,v out of “wireshark-1.0.5-1.el5_6.4″ in a promise and set promise_architectures to “x86_64″ – this will yield strange results). Either divine all three (n,v,a) from the promiser or just n.
[1] For more on epochs, see “When Version Numbers Aren’t Enough” at rpm.org