Cfengine configuration directory

Using the preview option with shellcommands

Relevant classes: any

The preview option allows commands to describe their actions when running cfengine in dry-run (-n) mode. That is, if you run "cfengine --dry-run --inform" the command can display what it will do. Syntax for the option is:

shellcommands:
    "command-string"
	preview=false/true

When preview is set true, the shellcommand will be executed when cfengine is in dry-run mode. When the preview options is false (the default) cfengine simply logs that that the command would be run, but does not actually run the command. A command with the preview option must check if the opt_dry_run class is defined and not actually perform any modifications if it is. The cfengine classes can be passed to the command by including the AllClasses macro in the command arguments or by running cfengine with the --use-env option to pass classes in the environment.

Additionally, the output of commands with the preview option are treated as cfengine log entries. By default, each line output by the command is logged as an error. Each line of output may begin with one of the following prefixes that indicates the priority of the log entry:

  • :editverbose:
  • :error:
  • :inform:
  • :logonly:
  • :silent:
  • :verbose:
If an output line has no prefix, it is logged as an error.

For example, here is a section of a cfengine script that uses a perl script to join a collection of mail aliases in the directory /etc/aliases.d and write them to the /etc/aliases file.

shellcommands:
    "$(scriptdir)/concatdir $(AllClasses) /etc/aliases.d /etc/aliases"
	preview=true

Here is the contents of the perl script:


#!/usr/local/bin/perl
#
#	concatdir
#
# Cfengine helper script that concatenates the contents of all files in a
# directory and write the result out to target file.

use strict;

my %class = ();

# Process command line.

if ($ARGV[0] =~ /^CFALLCLASSES=(.*)/) {
    # Get cfengine classes
    foreach my $name (split(':', $1)) {
	$class{$name} = 1;
    }
    shift;
}
my $update = !defined($class{'opt_dry_run'});

if (@ARGV != 2) {
    print STDERR <<EOF;
Usage: $0 [ CFALLCLASSES=class:... ] directory file
EOF
    exit 1;
}

ConcatDirectory($ARGV[0], $ARGV[1]);

exit 0;

#----------------------------------------------------------------------

sub ConcatDirectory ($$) {
    my ($srcdir, $target) = @_;
    my @srcfiles = glob("$srcdir/*");
    my $content = '';

    foreach my $file (@srcfiles) {
	open(FILE, "<$file") or die $!;
	$content .= join('', <FILE>);
	close(FILE);
    }

    WriteFile($target, $content);
}

# Joins all args and writes them out as an error message.

sub Error {
    print ":error:@_\n";
    exit 1;
}

# Joins all args and writes them out as an informational message (use for
# all modifications performed).

sub Info {
    print ":inform:@_\n";
}

# Joins all args and writes them out as a debug message.

sub Debug {
    print ":verbose:@_\n" if ($class{'opt_debug'});
}

# Create/replace $filename with data in $content - appends a newline.

sub WriteFile {
    my ($destpath, $content) = @_;

    if ( -f $destpath ) {
	my $oldcontent = ReadFile($destpath);
	return if ($content eq $oldcontent);
    }

    Info("writing $destpath");
    return unless ($update);

    open(FILE, ">$destpath") or die $!;
    print(FILE "$content\n") or die $!;
    close(FILE) or die $!;
}

# Read contents of named file, and return contents with trailing newline
# removed.

sub ReadFile {
    my ($filename) = @_;
    open(FILE, "<$filename") or die "can't open $filename: $!";
    my $contents = join('', <FILE>) ;
    close FILE;
    chomp($contents);

    return $contents;
}

Martin Andrews 2000
Back to documentation