Cfengine3 fix FreeBSD package name regex match

Cfengine 3 is a very cool piece of software and can have many uses, one thing it’s very good for is package management. FreeBSD works pretty well with this, but the big downfall is the lack of a really good package manager on FreeBSD. Sure there is ‘pkg_add’, ‘pkg_info’, ‘pkg_delete’, etc which is fine for the most part, but compare them to some of the others around such as apt for Debian or rpm for RedHat and it becomes apparent that FreeBSD just has a collection of utilities. This means you have to script around them yourself or use something like portmaster with a package only setting. There is apparently a new package tool on the way, so stay tuned for that.

You might be wondering why I don’t mention the ports collection, well it’s not because I don’t love it, I do. What I’m talking about here is building custom packages from the ports collection and then distributing them on mass.

While using cfengine for package management I came across an issue with the default library body for matching package names and versions. The issue is that the regex cannot handle package names with contain a hyphen (-) such as ‘vim-lite’ or ‘php5-gd’. Lets look at the default body to understand why.

body package_method freebsd
{
 package_changes => "individual";

 # Could use rpm for this
 package_list_command => "/usr/sbin/pkg_info";

 # Remember to escape special characters like |

 package_list_name_regex    => "([^-]+).*";
 package_list_version_regex => "[^-]+-([^s]+).*";

 package_name_regex    => "([^-]+).*";
 package_version_regex => "[^-]+-([^s]+).*";

 package_installed_regex => ".*";

 package_name_convention => "$(name)-$(version)";

package_add_command => "/usr/sbin/pkg_add -r";
package_delete_command => "/usr/sbin/pkg_delete";
}

The lines we’re interested in here are ‘package_list_name_regex’, ‘package_list_version_regex’, ‘package_name_regex’ and ‘package_version_regex’. If we read the regex (which is PCRE btw) for ‘package_name_regex’ we get, match everything thats *not* a hyphen (-) length one or more and capture it. Everything else, after the hyphen (-) is assumed to be the version number. This is further reinforced when we look at ‘package_version_regex’, which says match anything *not* a hyphen (-) length one or more followed by a hyphen (-). The remaining string is captured at the version. Let see how this plays out in the real world.

package_name_regex:
Pattern: ([^-]+).*
Subject: vim-lite-7.3.121

Result:
Array
(
[0] => vim-lite-7.3.121
[1] => vim
)

package_version_regex:
Pattern: [^-]+-([^s]+).*
Subject: vim-lite-7.3.121

Result:
Array
(
[0] => vim-lite-7.3.121
[1] => lite-7.3.121
)

As you can see the hyphen in the package name totally breaks this regex, according to cfengine the package name for ‘vim-lite-7.3.121’ is in fact ‘vim’ not ‘vim-lite’ and the version is ‘lite-7.3.121’ instead of ‘7.3.121’. This is a big problem, as it will be able to install the package if it doesn’t exists, but upgrades will break as it cannot correctly verify that the package is installed and what the version number is.

My solution to this is the following regex, seems to work fine, but maybe a PCRE regex guru out there can make it better? Please let me know.

package_name_regex:

Pattern: ^(S+)-d+.?+
Subject: vim-lite-7.3.121

Result:
Array
(
    [0] => vim-lite-7.
    [1] => vim-lite
)

package_version_regex:

Pattern: ^S+-((d+.?_?,?)+)
Subject: vim-lite-7.3.121

Result:
Array
(
    [0] => vim-lite-7.3.121
    [1] => 7.3.121
    [2] => 121
)

Element 1 is obviously the one we’re interested in here, and ultimately what cfengine will use. As you can see using this new regex pattern the name and version are matched correctly, and yes it works even if the package name does not include a hyphen (-). Let see how these changes make our cfengine config look.

body package_method freebsd
{
 package_changes => "individual";

 # Could use rpm for this
 package_list_command => "/usr/sbin/pkg_info";

 # Remember to escape special characters like |

 package_list_name_regex    => "^(S+)-d+.?+";
 package_list_version_regex => "^S+-((d+.?_?,?)+)";

 package_name_regex    => "^(S+)-d+.?+";
 package_version_regex => "^S+-((d+.?_?,?)+)";

 package_installed_regex => ".*";

 package_name_convention => "$(name)-$(version)";

package_add_command => "/usr/sbin/pkg_add -r";
package_delete_command => "/usr/sbin/pkg_delete";
}

I hope this helps someone out with the same issue that I had, if you have any comments or suggestions for improvement please share it here.

You may also like...

  • Tobias

    Thanks for your post.
    It helped me to develop an own package_method to install packages for octave.

    body package_method octave
    {
    package_changes => “bulk”;
    package_list_command => ‘/usr/bin/octave -qf package_versions.m’;
    package_list_update_ifelapsed => “100”;
    package_installed_regex => “.*”;
    package_list_name_regex => “^(S+)-d+.?+”;
    package_list_version_regex => “^S+-((d+.?_?,?)+)”;
    package_name_convention => “$(name)-$(version)”;
    package_add_command => ‘/usr/bin/octave -qf –no-window-system install.m ‘;
    package_delete_command => ‘/usr/bin/octave -qf –no-window-system uninstall.m ‘;
    package_name_regex => “^(S+)-d+.?+”;
    package_version_regex => “^S+-((d+.?_?,?)+)”;
    }

    package_versions.m, install.m and uninstall.m are octave-scripts for getting the package list
    and doing the installation (+some string formatting to answer cfengines expectations).

  • Tobias

    Thanks for your post.
    It helped me to develop an own package_method to install packages for octave.

    body package_method octave
    {
    package_changes => “bulk”;
    package_list_command => ‘/usr/bin/octave -qf package_versions.m’;
    package_list_update_ifelapsed => “100”;
    package_installed_regex => “.*”;
    package_list_name_regex => “^(S+)-d+.?+”;
    package_list_version_regex => “^S+-((d+.?_?,?)+)”;
    package_name_convention => “$(name)-$(version)”;
    package_add_command => ‘/usr/bin/octave -qf –no-window-system install.m ‘;
    package_delete_command => ‘/usr/bin/octave -qf –no-window-system uninstall.m ‘;
    package_name_regex => “^(S+)-d+.?+”;
    package_version_regex => “^S+-((d+.?_?,?)+)”;
    }

    package_versions.m, install.m and uninstall.m are octave-scripts for getting the package list
    and doing the installation (+some string formatting to answer cfengines expectations).