worrbase

Managing ports in FreeBSD with CFEngine3

2012-08-06

CFEngine is a fantastic tool for managing configuration files and packages across heterogeneous environments. I figured I could use it to maintain a basic set of installed packages across my Debian, OpenBSD and FreeBSD systems.

Except that the FreeBSD package_method promises in the cfengine standard library suck.

They use FreeBSD packages, and unless you have a box building your own sets of FreeBSD packages...packages suck. They are built with all of the bells and whistles, ergo they pull in a ton of shit you probably don't need on your system.

So I wrote a package_method promise that instead uses portmaster to install and remove packages. Here's the code:

body package_method freebsd_portmaster
{
    package_changes => "individual";

    package_list_command => "/usr/sbin/pkg_info";

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

    package_installed_regex => ".*";

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

    package_file_repositories => {
        "/usr/ports/accessibility/",
        "/usr/port/arabic/"
        "/usr/ports/archivers/",
        "/usr/ports/astro/",
        "/usr/ports/audio/",
        "/usr/ports/benchmarks/",
        "/usr/ports/biology/",
        "/usr/ports/cad/",
        "/usr/ports/chinese/",
        "/usr/ports/comms/",
        "/usr/ports/converters/",
        "/usr/ports/databases/",
        "/usr/ports/deskutils/",
        "/usr/ports/devel/",
        "/usr/ports/dns/",
        "/usr/ports/editors/",
        "/usr/ports/emulators/",
        "/usr/ports/finance/",
        "/usr/ports/french/",
        "/usr/ports/ftp/",
        "/usr/ports/games/",
        "/usr/ports/german/",
        "/usr/ports/graphics/",
        "/usr/ports/hebrew/",
        "/usr/ports/hungarian/",
        "/usr/ports/irc/",
        "/usr/ports/japanese/",
        "/usr/ports/java/",
        "/usr/ports/korean/",
        "/usr/ports/lang/",
        "/usr/ports/mail/",
        "/usr/ports/math/",
        "/usr/ports/mbone/",
        "/usr/ports/misc/",
        "/usr/ports/multimedia/",
        "/usr/ports/net/",
        "/usr/ports/net-im/",
        "/usr/ports/net-mgmt/",
        "/usr/ports/net-p2p/",
        "/usr/ports/news/",
        "/usr/ports/packages/",
        "/usr/ports/palm/",
        "/usr/ports/polish/",
        "/usr/ports/ports-mgmt/",
        "/usr/ports/portuguese/",
        "/usr/ports/print/",
        "/usr/ports/russian/",
        "/usr/ports/science/",
        "/usr/ports/security/",
        "/usr/ports/shells/",
        "/usr/ports/sysutils/",
        "/usr/ports/textproc/",
        "/usr/ports/ukrainian/",
        "/usr/ports/vietnamese/",
        "/usr/ports/www/",
        "/usr/ports/x11/",
        "/usr/ports/x11-clocks/",
        "/usr/ports/x11-drivers/",
        "/usr/ports/x11-fm/",
        "/usr/ports/x11-fonts/",
        "/usr/ports/x11-servers/",
        "/usr/ports/x11-themes/",
        "/usr/ports/x11-toolkits/",
        "/usr/ports/x11-wm/",
    };

    package_add_command => "/usr/local/sbin/portmaster -D -G --no-confirm";
    package_update_command => "/usr/local/sbin/portmaster -D -G --no-confirm";
    package_delete_command => "/usr/local/sbin/portmaster --no-confirm -e";

    expireafter => 240;
}

The only interesting thing about the body of the promise is that I'm using package_file_repositories in a way that it was never intended to be used. Since the port names needed to be provided to portmaster in a / format, I'm leveraging a cfengine trick to kind of fake that. If you provide a package_file_repositories clause, cfengine will prepend a variable called $(firstrepo) to the name of every package that it adds to the package_add_command line.

Armed with this knowledge, we just add a package_file_repositories entry for each port category directory, and the problem's been solved.