CFEngine Solutions


Next: , Previous: (dir), Up: (dir)

CFEngine-Solutions

COMPLETE TABLE OF CONTENTS

Summary of contents


Next: , Previous: Top, Up: Top

1 Introduction

CFEngine 3 has been redesigned to allow modular solution building in terms of a simple, regular language. This guide explains how to use the CFEngine Community Open Promise-Body Library to express some simple idioms and approaches for solving common system problems.

The solutions presented here are necessarily generic and incomplete, since local environmental issues always define the boundaries of a solution in a real setting. A solution guide does not present finished solutions, but rather hints about how to achieve such solutions with standardized idioms.

CFEngine gives you great freedom to craft specific solutions, without the need for low level coding. For most tasks, you will find the standardized templates sufficient for your needs, but the possibilties do not stop there.

If you need assistance in formulating specific solutions for your system environment, contact CFEngine's Professional Services at contact@cfengine.com;


Previous: Introduction, Up: Introduction

1.1 Begin - Get Started

To get started with CFEngine, you can use the following template for entering examples.

body common control
{
bundlesequence => { "main" };
inputs => { "cfengine_stdlib.cf" };
}


bundle agent main
{
# example

}

Then you enter the cases as below. The general pattern of the syntax is like this (red: cfengine word, blue: user-defined word):
# The general pattern

bundle component name(parameters)
{
what_type:
 where_when::

  # Traditional comment



  "promiser" -> { "promisee1", "promisee2" },
        comment => "The intention ...",
         handle => "unique_id_label",
    attribute_1 => body_or_value1,
    attribute_2 => body_or_value2;
}


Next: , Previous: Begin - Get started, Up: Begin - Get started

1.1.1 Creating files and directories

files:

  "/home/mark/tmp/test_plain"
     perms => mog("644", "root", "wheel"),
     create => "true";


  "/home/mark/tmp/test_dir/."
     perms => mog("644", "root", "wheel"),  
     # will add +x for dirs, see also rxdirs

     create => "true";


Next: , Previous: Creating files and directories, Up: Begin - Get started

1.1.2 Copy single files

files:

  "/home/mark/tmp/test_plain"

    copy_from => local_cp("$(sys.workdir)\bin\file");

  "/home/mark/tmp/test_dir"

    copy_from => 
        secure_cp("$(sys.workdir)/bin","serverhost");


Next: , Previous: Copy single files, Up: Begin - Get started

1.1.3 Copy directory trees

files:

  "/home/mark/tmp/test_dir"

    copy_from => local_cp("$(sys.workdir)\bin\."),
 depth_search => recurse("inf");


  "/home/mark/tmp/test_dir"

  copy_from => secure_cp("$(sys.workdir)/bin","serverhost"),
 depth_search => recurse("inf");


Next: , Previous: Copy directory trees, Up: Begin - Get started

1.1.4 Editing password or group files

vars:

 "userset" slist => { "user1", "user2", "user3" };

files:

  "/home/mark/tmp/passwd"
     edit_line => 
        set_user_field("mark","7","/set/this/shell");

  "/home/mark/tmp/group"
     edit_line => 
        append_user_field("root","4","@(main.userset)");


Next: , Previous: Editing password or group files, Up: Begin - Get started

1.1.5 Disabling and rotating files

files:

  "/home/mark/tmp/test_create"
      rename => disable;

 "/home/mark/tmp/rotateme"
      rename => rotate("4");


Next: , Previous: Disabling and rotating files, Up: Begin - Get started

1.1.6 Hashing for change detection (tripwire)

files:

  "/home/mark/tmp" -> "me"
       changes      => detect_all_change,
       depth_search => recurse("inf"),
       action       => background;

  "/home/mark/LapTop/words" -> "you"
       changes      => detect_all_change,
       depth_search => recurse("inf");


Next: , Previous: Hashing for change detection - tripwire, Up: Begin - Get started

1.1.7 Command or script execution

commands:

 Sunday.Hr04.Min05_10.myhost::

  "/usr/bin/update_db";

 any::

  "/etc/mysql/start"

      contain => setuid("mysql");


Next: , Previous: Command or script execution, Up: Begin - Get started

1.1.8 Kill process

processes:

  "snmpd"

     signals => { "term", "kill" };


Next: , Previous: Kill process, Up: Begin - Get started

1.1.9 Restart process

processes:

  "httpd"

     restart_class => "lift_off";

commands:

 lift_off::

    "/etc/init.d/apache2 restart";

Why? Separating this into two parts gives a high level of control and conistency to CFEngine. There are many options for command execution, like the ability to run commands in a sandbox or as `setuid'. These should not be reproduced in processes.


Next: , Previous: Restart process, Up: Begin - Get started

1.1.10 Check filesystem space

storage:

  "/usr" volume  => mycheck("10%");


Next: , Previous: Check filesystem space, Up: Begin - Get started

1.1.11 Mount a filesystem

storage:

 "/home/mark/server_home"

  mount => nfs("myserver","/home/mark");


Previous: Mount a filesystem, Up: Begin - Get started

1.1.12 Software and patch installation

packages:

  "apache2"

     package_policy => "add",
     package_method => generic;


Next: , Previous: Introduction, Up: Top

2 Common high level issues


Next: , Previous: Common high level issues, Up: Common high level issues

2.1 Centralized Management

These examples show a simple setup for starting with a central approach to management of servers. Centralization of management is a simple approach suitable for small environments with few requirements. It is useful for clusters where systems are all alike.


Next: , Previous: Centralized Management, Up: Centralized Management

2.1.1 All hosts the same

This shows the simplest approach in which all hosts are the same. It is too simple for most environments, but it serves as a starting point. Compare it to the next section that includes variation.

body common control
   {
   bundlesequence  => { "central" };
   }


############################################



bundle agent central

{
vars:

  "policy_server" string => "myhost.domain.tld";

  "mypackages" slist => {
                        "nagios"
                        "gcc",
                        "apache2",
                        "php5"
                        };

files:

  # Password management can be very simple if all hosts are identical



  "/etc/passwd"

    comment   => "Distribute a password file",
    perms     => mog("644","root","root"),
    copy_from => secure_cp("/home/mark/LapTop/words/RoadAhead","$(policy_server)");

packages:

  "$(mypackages)"

     package_policy => "add",
     package_method => generic;


# Add more promises below ...



}


#########################################################

# Server config

#########################################################



body server control

{
allowconnects         => { "127.0.0.1" , "::1", "10.20.30" };
allowallconnects      => { "127.0.0.1" , "::1", "10.20.30" };
trustkeysfrom         => { "127.0.0.1" , "::1", "10.20.30" };
# allowusers

}

#########################################################



bundle server access_rules()

{
access:

 # myhost.domain.tld makes this file available to 10.20.30*



 myhost_domain_tld::

  "/etc/passwd"

     admit   => { "127.0.0.1", "10.20.30" };
}


Next: , Previous: All hosts the same, Up: Centralized Management

2.1.2 Variation in hosts

body common control
   {
   bundlesequence  => { "central" };
   }


############################################



bundle agent central

{
classes:

  "mygroup_1" or => { "myhost", "host1", "host2", "host3" };
  "mygroup_2" or => { "host4", "host5", "host6" };

vars:

  "policy_server" string => "myhost.domain.tld";

 mygroup_1::

  "mypackages" slist => {
                        "nagios"
                        "gcc",
                        "apache2",
                        "php5"
                        };

 mygroup_2::

  "mypackages" slist => {
                        "apache"
                        "mysql",
                        "php5"
                        };


files:

  # Password management can be very simple if all hosts are identical



  "/etc/passwd"

    comment   => "Distribute a password file",
    perms     => mog("644","root","root"),
    copy_from => secure_cp("/etc/passwd","$(policy_server)");

packages:

  "$(mypackages)"

     package_policy => "add",
     package_method => generic;


# Add more promises below ...



}


#########################################################

# Server config

#########################################################



body server control

{
allowconnects         => { "127.0.0.1" , "::1", "10.20.30" };
allowallconnects      => { "127.0.0.1" , "::1", "10.20.30" };
trustkeysfrom         => { "127.0.0.1" , "::1", "10.20.30" };
# allowusers

}

#########################################################



bundle server access_rules()

{
access:

 # myhost.domain.tld makes this file available to 10.20.30*



 myhost_domain_tld::

  "/etc/passwd"

     admit   => { "127.0.0.1", "10.20.30" };
}


Previous: Variation in hosts, Up: Centralized Management

2.1.3 Updating from a central hub

The configuration bundled with the CFEngine source code contains an example of centralized updating of policy that covers more subtleties than this example, and handles fault tolerance. Here is the main idea behind it. For simplicity, we assume that all hosts are on network 10.20.30.* and that the central policy server/hub is 10.20.30.123.

bundle agent update
{
vars:

 "master_location" string => "/var/cfengine/masterfiles";

 "policy_server"   string => "10.20.30.123";
                   comment => "IP address to locate your policy host.";

files:

  "$(sys.workdir)/inputs"

    perms => system("600"),
    copy_from => remote_cp("$(master_location)",$(policy_server)),
    depth_search => recurse("inf");

  "$(sys.workdir)/bin"

    perms => system("700"),
    copy_from => remote_cp("/usr/local/sbin","localhost"),
    depth_search => recurse("inf");
}

#######################################################



body server control

{
allowconnects         => { "127.0.0.1" , "10.20.30" };
allowallconnects      => { "127.0.0.1" , "10.20.30" };
trustkeysfrom         => { "127.0.0.1" , "10.20.30" };
}

#######################################################



bundle server access_rules()
{
access:

 10_20_30_123::

  "/var/cfengine/masterfiles"

    admit   => { "127.0.0.1", "10.20.30" };
}



Next: , Previous: Centralized Management, Up: Common high level issues

2.2 Change detection

body common control

{
bundlesequence  => { "testbundle"  };

inputs => { "cfengine_stdlib.cf" };
}

########################################################



bundle agent testbundle

{
files:

  "/usr"

       changes      => detect_all_change,
       depth_search => recurse("inf"),
       action       => background;
}


Next: , Previous: Change detection, Up: Common high level issues

2.3 Garbage collection

bundle agent garbage_collection
{
files:

 Sunday::

  "$(sys.workdir)/nova_repair.log"

    comment => "Rotate the promises repaired logs each week",
    rename => rotate("7"),
    action => if_elapsed("10000");

  "$(sys.workdir)/nova_notkept.log"

    comment => "Rotate the promises not kept logs each week",
    rename => rotate("7"),
    action => if_elapsed("10000");

  "$(sys.workdir)/promise.log"

    comment => "Rotate the promises not kept logs each week",
    rename => rotate("7"),
    action => if_elapsed("10000");

 any::

  "$(sys.workdir)/outputs"

    comment => "Garbage collection of any output files",
    delete => tidy,
    file_select => days_old("3"),
    depth_search => recurse("inf");

  "$(sys.workdir)/"

    comment => "Garbage collection of any output files",
    delete => tidy,
    file_select => days_old("14"),
    depth_search => recurse("inf");

  # Other resources



  "/tmp"

    comment => "Garbage collection of any temporary files",
    delete => tidy,
    file_select => days_old("3"),
    depth_search => recurse("inf");

  "/var/log/apache2/.*bz"

    comment => "Garbage collection of rotated log files",
    delete => tidy,
    file_select => days_old("30"),
    depth_search => recurse("inf");

  "/var/log/apache2/.*gz"

    comment => "Garbage collection of rotated log files",
    delete => tidy,
    file_select => days_old("30"),
    depth_search => recurse("inf");

  "/var/log/zypper.log"

    comment => "Prevent the zypper log from choking the disk",
    rename => rotate("0"),
    action => if_elapsed("10000");

}



Next: , Previous: Garbage collection, Up: Common high level issues

2.4 Distribute root passwords

######################################################################
#

# Root password distribution

#

######################################################################



body common control

{
version => "1.2.3";
bundlesequence  => { "SetRootPassword" };
}

########################################################



bundle common g
{
vars:

  "secret_keys_dir" string => "/tmp";
}

########################################################



bundle agent SetRootPassword

{
files:

  "/var/cfengine/ppkeys/rootpw.txt"

      copy_from => scp("$(sys.fqhost)-root.txt","master_host.example.org");

      # or $(pw_class)-root.txt



  # Or get variables directly from server woth Nova



 "remote-passwd" string => remotescalar("rem_password","127.0.0.1","yes");

  # Test this on a copy



  "/tmp/shadow"

       edit_line => SetRootPw;

}

########################################################



bundle edit_line SetRootPw
  {
  vars:

   # Assume this file contains a single string of the form root:passwdhash:

   # with : delimiters to avoid end of line/file problems



   "pw" int => readstringarray("rpw","$(sys.workdir)/ppkeys/rootpw.txt",
                                                    "#[^\n]*",":","1","200");

  field_edits:

   "root:.*"

      # Set field of the file to parameter



      edit_field => col(":","2","$(rpw[1])","set");
  }

########################################################



bundle server passwords
{
vars:

  # Read a file of format

  #

  # classname: host1,host2,host4,IP-address,regex.*,etc

  #



       "pw_classes" int => readstringarray("acl","$(g.secret_keys_dir)/classes.txt",
                                                       "#[^\n]*",":","100","4000");
  "each_pw_class" slist => getindices("acl");

access:

  "/secret/keys/$(each_pw_class)-root.txt"

        admit   => splitstring("$(acl[$(each_pw_class)][1])" , ":" , "100"),
    ifencrypted => "true";

}


Next: , Previous: Distribute root passwords, Up: Common high level issues

2.5 Distribute ssh keys

# Assume that we have collected all users' public keys into a single source area
# on the server. First copy the ones we need to localhost, and then edit them into

# the the user's local keyring.



  # vars:

  #

  #  "users" slist => { "user1", "user2", ...};

  #

  # methods:

  #

  #  "any" usebundle => allow_ssh_login_from_authorized_keys(@(users),"sourcehost");

  #



########################################################################



bundle agent allow_ssh_rootlogin_from_authorized_keys(user,sourcehost)
{
vars:

  "local_cache"       string => "/var/cfengine/ssh_cache"; 
  "authorized_source" string => "/master/cfengine/ssh_keys";

files:

   "$(local_cache)/$(user).pub"

         comment => "Copy public keys from a an authorized cache into a cache on localhost",
           perms => mo("600","root"),
       copy_from => remote_cp("$(authorized_source)/$(user).pub","$(sourcehost)"),
          action => if_elapsed("60");

   "/root/.ssh/authorized_keys"

         comment => "Edit the authorized keys into the user's personal keyring",
       edit_line => insert_file_if_no_line_matching("$(user)","$(local_cache)/$(user).pub"),
          action => if_elapsed("60");
}

########################################################################



bundle agent allow_ssh_login_from_authorized_keys(user,sourcehost)
{
vars:

  "local_cache"       string => "/var/cfengine/ssh_cache"; 
  "authorized_source" string => "/master/cfengine/ssh_keys";

files:

   "$(local_cache)/$(user).pub"

         comment => "Copy public keys from a an authorized cache into a cache on localhost",
           perms => mo("600","root"),
       copy_from => remote_cp("$(authorized_source)/$(user).pub","$(sourcehost)"),
          action => if_elapsed("60");

   "/home/$(user)/.ssh/authorized_keys"

         comment => "Edit the authorized keys into the user's personal keyring",
       edit_line => insert_file_if_no_line_matching("$(user)","$(local_cache)/$(user).pub"),
          action => if_elapsed("60");
}

########################################################################



bundle edit_line insert_file_if_no_line_matching(user,file),
{
classes:

  have_user expression => regline("$(user).*","$(this.promiser)");

insert_lines:

  !have_user::

    "$(file)"
         insert_type => "file";
}


Next: , Previous: Distribute ssh keys, Up: Common high level issues

2.6 Laptop support configuration

Laptops do not need a lot of confguration support. IP addresses are set by DHCP and conditions are changeable. But you want to set your DNS search domains to familiar settings in spite of local DHCP configuration, and another useful trick is to keep a regular backup of disk changes on the local disk. This won't help against disk destruction, but it is a huge advantage when your user accidentally deletes files while travelling or offline.

#######################################################
#

# Laptop

#

#######################################################



body common control

{
bundlesequence  => {
                   "update",
                   "garbage_collection",
                   "main",
                   "backup",
                   };

inputs          => {
                   "update.cf",
                   "site.cf",
                   "library.cf"
                   };
}

#######################################################



body agent control
{
# if default runtime is 5 mins we need this for long jobs

ifelapsed => "15";
}

#######################################################



body monitor control
{
forgetrate => "0.7";
}

#######################################################



body executor control

{
splaytime => "1";
mailto => "mark@iu.hio.no";
smtpserver => "localhost";
mailmaxlines => "30";

# Instead of a separate update script, now do this



exec_command => "$(sys.workdir)/bin/cf-agent -f failsafe.cf && $(sys.workdir)/bin/cf-agent";
}

#######################################################

# General site issues can be in bundles like this one

#######################################################



bundle agent main

{
vars:

  "component" slist => { "cf-monitord", "cf-serverd" };

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



files:

  "$(sys.resolv)"  # test on "/tmp/resolv.conf" #

     create        => "true",
     edit_line     => resolver,
     edit_defaults => def;

processes:

  "$(component)" restart_class => canonify("start_$(component)");

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



commands:

   "$(sys.workdir)/bin/$(component)"

       ifvarclass => canonify("start_$(component)");
}

#######################################################

# Backup

#######################################################



bundle agent backup
{
files:

  "/home/backup"

     copy_from => cp("/home/mark"),
  depth_search => recurse("inf"),
   file_select => exclude_files,
        action => longjob;

}

#######################################################

# Garbage collection issues

#######################################################



bundle agent garbage_collection
{
files:

  "$(sys.workdir)/outputs"

    delete => tidy,
    file_select => days_old("3"),
    depth_search => recurse("inf");


}


Next: , Previous: Laptop support configuration, Up: Common high level issues

2.7 Find the MAC address

Finding the ethernet address can be hard, but on Linux it is straightforward.

bundle agent test
{
vars:

linux::
 "interface" string => execresult("/sbin/ifconfig eth0","noshell");

solaris::
 "interface" string => execresult("/usr/sbin/ifconfig bge0","noshell");

freebsd::
 "interface" string => execresult("/sbin/ifconfig le0","noshell");

darwin::
 "interface" string => execresult("/sbin/ifconfig en0","noshell");

classes:

 linux::

   "ok" expression => regextract(
                                ".*HWaddr ([^\s]+).*(\n.*)*",
                                "$(interface)",
                                "mac"
                                );

 solaris::

   "ok" expression => regextract(
                                ".*ether ([^\s]+).*(\n.*)*",
                                "$(interface)",
                                "mac"
                                );

 freebsd::

   "ok" expression => regextract(
                                ".*ether ([^\s]+).*(\n.*)*",
                                "$(interface)",
                                "mac"
                                );

 darwin::

   "ok" expression => regextract(
                                "(?s).*ether ([^\s]+).*(\n.*)*",
                                "$(interface)",
                                "mac"
                                );

reports:

ok::

  "MAC address is $(mac[1])";

}


Next: , Previous: Find MAC address, Up: Common high level issues

2.8 Log rotation

body common control
   {
   bundlesequence  => { "testbundle" };
   }


############################################



bundle agent testbundle

{
files:

  "/home/mark/tmp/rotateme"

      rename => rotate("4");
}

############################################



body rename rotate(level)

{
rotate => "$(level)";
}


Next: , Previous: Log rotation, Up: Common high level issues

2.9 Manage a system file


Next: , Previous: Manage a system file, Up: Manage a system file

2.9.1 Simple template

bundle agent hand_edited_config_file
{
vars:

  "file_template" string => "$(level)";

"
# Syntax:

#

# IP-Address  Full-Qualified-Hostname  Short-Hostname

#



127.0.0.1       localhost
::1             localhost ipv6-localhost ipv6-loopback
fe00::0         ipv6-localnet
ff00::0         ipv6-mcastprefix
ff02::1         ipv6-allnodes
ff02::2         ipv6-allrouters
ff02::3         ipv6-allhosts
10.0.0.100      host1.domain.tld host1
10.0.0.101      host2.domain.tld host2
10.0.0.20       host3.domain.tld host3
10.0.0.21       host4.domain.tld host4
";

##############################################################



files:

   "/etc/hosts"

       comment => "Define the content of all host files from this master source",
        create => "true",
     edit_line => append_if_no_lines("$(file_template)"),
 edit_defaults => empty,
         perms => mo("$(mode)","root"),
        action => if_elapsed("60");
}


Next: , Previous: Simple template, Up: Manage a system file

2.9.2 Simple versioned template

The simplest approach to managing a file is to maintain a master copy by hand, keeping it in a version controlled repository (e.g. svn), and installing this version on the end machine.

We'll assume that you have a version control repository that is located on some independent server, and has been checked out manually once (with authentication) in /mysite/masterfiles.

bundle agent hand_edited_config_file
{
vars:

  "masterfiles"   string => "/mysite/masterfiles";
  "policy_server" string => "policy_host.domain.tld";

files:

   "/etc/hosts"

        comment => "Synchronize hosts with a hand-edited template in svn",
          perms => m("644"),
      copy_from => remote_cp("$(masterfiles)/trunk/hosts_master","$(policy_server)");

commands:


   "/usr/bin/svn update"

        comment => "Update the company document repository including manuals to a local copy",
        contain => silent_in_dir("$(masterfiles)/trunk"),
     ifvarclass => canonify("$(policy_server)");

}


Next: , Previous: Simple versioned template, Up: Manage a system file

2.9.3 Macro template

The next simplest approach to file management is to add variables to the template that will be expanded into local values at the end system, e.g. using variables like ‘$(sys.host)’ for the name of the host within the body of the versioned template.

bundle agent hand_edited_template
{
vars:

  "masterfiles"   string => "/mysite/masterfiles";
  "policy_server" string => "policy_host.domain.tld";

files:

   "/etc/hosts"

        comment => "Synchronize hosts with a hand-edited template in svn",
          perms => m("644"),
        create => "true",
     edit_line => expand_template("$(masterfiles)/trunk/hosts_master"),
 edit_defaults => empty,
        action => if_elapsed("60");

commands:


   "/usr/bin/svn update"

        comment => "Update the company document repository including manuals to a local copy",
        contain => silent_in_dir("$(masterfiles)/trunk"),
     ifvarclass => canonify("$(policy_server)");

}

The macro template file may contain variables, as below, that get expanded by CFEngine.

     # Syntax:
     #
     # IP-Address  Full-Qualified-Hostname  Short-Hostname
     #

     127.0.0.1       localhost $(sys.host)
     ::1             localhost ipv6-localhost ipv6-loopback
     fe00::0         ipv6-localnet
     ff00::0         ipv6-mcastprefix
     ff02::1         ipv6-allnodes
     ff02::2         ipv6-allrouters
     ff02::3         ipv6-allhosts
     10.0.0.100      host1.domain.tld host1
     10.0.0.101      host2.domain.tld host2
     10.0.0.20       host3.domain.tld host3
     10.0.0.21       host4.domain.tld host4

     # Add below this line

     $(definitions.more_hosts)


Previous: Macro template, Up: Manage a system file

2.9.4 Custom editing

If you do not control the starting state of the file, because it is distributed by an operating system vendor for instance, then editing the final state is the best approach. That way, you will get changes that are made by the vendor, and will ensure your own modifications are kept even when updates arrive.

bundle agent modifying_managed_file
{
vars:

  "data"   slist => { "10.1.2.3 sirius", "10.1.2.4 ursa-minor", "10.1.2.5 orion"};

files:

   "/etc/hosts"

        comment => "Append a list of lines to the end of a file if they don't exist",
          perms => m("644"),
        create => "true",
     edit_line => append_if_no_lines("modifying_managed_file.data"),
        action => if_elapsed("60");


}

Another example shows how to set the values of variables using a data-driven approach and methods from the standard library.

#######################################################
#

# Edit variable = value in a text file

#

#######################################################



body common control

{
bundlesequence  => { "testsetvar" };
}

#######################################################



bundle agent testsetvar

{
vars:

  "v[variable_1]" string => "value_1";
  "v[variable_2]" string => "value_2";

files:

  "/tmp/test_setvar"

     edit_line => set_variable_values("testsetvar.v");
}


Next: , Previous: Manage a system file, Up: Common high level issues

2.10 Manage a system process


Next: , Previous: Manage a system process, Up: Manage a system process

2.10.1 Ensure running

The simplest example might look like this:

bundle agent restart_process
{
processes:

  "httpd"

      comment => "Make sure apache web server is running",
      restart_class => "restart_httpd";

commands:

 restart_httpd::

     "/etc/init.d/apache2 restart";

}

This example shows how the CFEngine components could be started using a pattern.

bundle agent cfengine_processes
{
vars:

  "components" slist => { "cf-execd", "cf-monitord", "cf-serverd", "cf-hub" };

processes:

  "$(components)"

      comment => "Make sure server parts of cfengine are running",
      restart_class => canonify("start_$(component)");

commands:

   "$(sys.workdir)/bin/$(component)"

          comment => "Make sure server parts of cfengine are running",
       ifvarclass => canonify("start_$(components)");

}


Next: , Previous: Ensure running, Up: Manage a system process

2.10.2 Ensure not running

bundle agent restart_process
{
vars:

  "killprocs" slist => { "snmpd", "gameserverd", "irc", "crack" };

processes:

  "$(killprocs)"

      comment => "Make processes are not running",
      signals => { "term", "kill" };
;
}


Previous: Ensure not running, Up: Manage a system process

2.10.3 Prune processes

This example kills processes owned by a particular user that have exceeded 100000 bytes of resident memory.

body common control
{
bundlesequence  => { "testbundle"  };
}

########################################################



bundle agent testbundle

{
processes:

 ".*"

    process_select  => big_processes("mark"),
            signals => { "term" };
}

########################################################



body process_select big_processes(o)

{
process_owner => { "$(o)" };
rsize => irange("100000","900000");
process_result => "rsize.process_owner";
}


Next: , Previous: Manage a system process, Up: Common high level issues

2.11 Manage users

There are many approaches to managing users. You can edit system files like /etc/passwd directly, or you can use commands on some systems like ‘useradd’ or ‘adduser’. In all cases it is desirable to make this a data-driven process.


Next: , Previous: Manage users, Up: Manage users

2.11.1 Add users

A simple approach which adds new users to the password file, and to a group called ‘users’ in the group file. Is shown below. This example does not edit the shadow file. A simple pattern that can be modified for use is shown below.

Note that, although this is a simple minded approach, it is the most efficient of the approaches shown here as all operations can be carried out in a single operation for each file.

bundle agent addusers
{
vars:

  # Add some users



  "pw[mark]" string => "mark:x:1000:100:Mark Burgess:/home/mark:/bin/bash";
  "pw[fred]" string => "fred:x:1001:100:Right Said:/home/fred:/bin/bash";
  "pw[jane]" string => "jane:x:1002:100:Jane Doe:/home/jane:/bin/bash";

  "users" slist => getindices("pw");

files:

  "/etc/passwd"
     edit_line => append_users_starting("addusers.pw");

#  "/etc/shadow"

#     edit_line => append_users_starting("$(users):defaultpasswd:::::::");



  "/etc/group"
       edit_line => append_user_field("users","4","@(addusers.users)");

  "/home/$(users)/."

     create => "true",
      perms => mog("755","$(users)","users");
}

A second approach is to use the shell commands supplied by some operating systems; this assumes that suitable defaults have been set up manually. Also the result is not repairable in a simple convergent manner. The command needs to edit multiple files for each user, and is quite inefficient.

bundle agent addusers
{
vars:

  "users" slist => { "mark", "fred", "jane" };

commands:

   "/usr/sbin/useradd $(users)";
}

An alternative approach is to use a method to wrap around the handling of a user. Although this looks nice, it is less efficient than the first method because it must edit the files multiple times.
bundle agent addusers
{
vars:

  # Add some users



  "pw[mark]" string => "mark:x:1000:100:Mark Burgess:/home/mark:/bin/bash";
  "pw[fred]" string => "fred:x:1001:100:Right Said:/home/fred:/bin/bash";
  "pw[jane]" string => "jane:x:1002:100:Jane Doe:/home/jane:/bin/bash";

  "users" slist => getindices("pw");

methods:

  "any" usebundle => user_add("$(users)","$(pw[$(users)])");

}

bundle agent user_add(x,pw)
{
files:

  "/etc/passwd"
     edit_line => append_users_starting("addusers.pw");

#  "/etc/shadow"

#     edit_line => append_users_starting("$(users):defaultpasswd:::::::");



  "/etc/group"
       edit_line => append_user_field("users","4","@(addusers.users)");

  "/home/$(users)/."

     create => "true",
      perms => mog("755","$(users)","users");
}


Previous: Add users, Up: Manage users

2.11.2 Remove users


Next: , Previous: Manage users, Up: Common high level issues

2.12 Postfix mail configuration

#######################################################
#

# Postfix

#

#######################################################



body common control

{
any::

  bundlesequence  => {
                     postfix
                     };
}

#######################################################



bundle agent postfix

{
vars:

 "prefix"     string => "/etc";
 "smtpserver" string => "localhost";
 "mailrelay"  string => "mailx.example.org";

files:

  "$(prefix)/main.cf"
      edit_line => prefix_postfix;

  "$(prefix)/sasl-passwd"
      create    => "true",
      perms     => mo("0600","root"),
      edit_line => append_if_no_line("$(smtpserver) _$(sys.fqhost):chmsxrcynz4etfrejizhs22");
}

#######################################################

# For the library

#######################################################



bundle edit_line prefix_postfix

{
#

# Value have the form NAME = "quoted space separated list"

#

vars:

  "ps[relayhost]"                  string => "[$(postfix.mailrelay)]:587";
  "ps[mydomain]"                   string => "iu.hio.no";
  "ps[smtp_sasl_auth_enable]"      string => "yes";
  "ps[smtp_sasl_password_maps]"    string => "hash:/etc/postfix/sasl-passwd";
  "ps[smtp_sasl_security_options]" string => "";
  "ps[smtp_use_tls]"               string => "yes";
  "ps[default_privs]"              string => "mailman";
  "ps[inet_protocols]"             string => "all";
  "ps[inet_interfaces]"            string => "127.0.0.1";

  "parameter_name" slist => getindices("ps");

delete_lines:

  "$(parameter_name).*";

insert_lines:

  "$(parameter_name) = $(ps[$(parameter_name)])";

}

########################################################



bundle edit_line AppendIfNSL(parameter)
  {
  insert_lines:

    "$(parameter)"; # This is default
  }


Next: , Previous: Postfix mail configuration, Up: Common high level issues

2.13 Set up HPC clusters

HPC cluster machines are usually all identical, so the cfengine configuration is very simple. HPC clients value CPU and memory resources, so we can shut down unnecessary services to save CPU. We can also change the scheduling rate of cfengine to run less frequently, and save a little:

#######################################################



body executor control

{
splaytime => "1";
mailto => "cfengine@example.com";
smtpserver => "localhost";
mailmaxlines => "30";

# Once per hour, on the hour



schedule     => { "Min00_05" };
}

#######################################################



bundle agent services_disable
{
vars:

   # list all of xinetd services (case sensitive)



   "xinetd_services" slist => {
                               "imap",
                               "imaps",
                               "ipop2",
                               "ipop3",
                               "krb5-telnet",
                               "klogin",
                               "kshell",
                               "ktalk",
                               "ntalk",
                               "pop3s",

                              };
methods:

   # perform the actual disable all xinetd services according to the list above



   "any"  usebundle => disable_xinetd("$(xinetd_services)");

processes:

  "$(xinetd_services)"

           signals => { "kill" };

}

###############################################################################



bundle agent disable_xinetd(name)
{
 vars:
   "status" string => execresult("/sbin/chkconfig --list $(name)", "useshell");

 classes:
   "on"  expression => regcmp(".*on.*","$(status)");

 commands:
   on::
      "/sbin/chkconfig $(name) off",
        comment => "disable $(name) service";

 reports:
   on::
      "disable $(name) service.";

}


Next: , Previous: Set up HPC clusters, Up: Common high level issues

2.14 Set up name resolution

There are many ways to do name resolution setup1 We write a reusable bundle using the editing features.

A simple and straightforward approach is to maintain a separate modular bundle for this task. This avoids too many levels of abstraction and keeps all the information in one place. We implement this as a simple editing promise for the /etc/resolv.conf file.

bundle agent system_files

{
files:

  "$(sys.resolv)"  # test on "/tmp/resolv.conf" #

     comment       => "Add lines to the resolver configuration",
     create        => "true",
     edit_line     => resolver,
     edit_defaults => std_edits;

   # ...other system files ...



}

#######################################################



bundle edit_line resolver

{
delete_lines:

  # delete any old name servers or junk we no longer need



  "search.*";
  "nameserver 80.65.58.31";
  "nameserver 80.65.58.32";
  "nameserver 82.103.128.146";
  "nameserver 78.24.145.4";
  "nameserver 78.24.145.5";
  "nameserver 128.39.89.10";

insert_lines:

  "search mydomain.tld" location => start;

 special_net::

  "nameserver 128.39.89.8";
  "nameserver 128.39.74.66";

 !special_net::

  "nameserver 128.38.34.12";

 any::

  "nameserver 212.112.166.18";
  "nameserver 212.112.166.22";
}

A second approach is to try to conceal the operational details behind a veil of abstraction.

bundle agent system_files
{
vars:

 "searchlist"  string => "iu.hio.no cfengine.com";

 "nameservers" slist => {
                        "128.39.89.10",
                        "128.39.74.16",
                        "192.168.1.103"
                        };
files:

  "$(sys.resolv)"  # test on "/tmp/resolv.conf" #
      create        => "true",
      edit_line     => doresolv("$(s)","@(this.n)"),
      edit_defaults => empty;

 # ....



}

#######################################################



bundle edit_line doresolv(search,names)

{
insert_lines:
  "search $(search)";
  "nameserver $(names)";
}

DNS is not the only name service, of course. Unix has its older /etc/hosts file which can also be managed using file editing. We simply append this to the system_files bundle.

bundle agent system_files
{

# ...



files:

   "/etc/hosts"

     comment => "Add hosts to the /etc/hosts file",
   edit_line => fix_etc_hosts;
}

###########################################################



bundle edit_line fix_etc_hosts
{
vars:

 "names[127.0.0.1]"    string => "localhost localhost.cfengine.com";
 "names[128.39.89.12]" string => "myhost myhost.cfengine.com";
 "names[128.39.89.13]" string => "otherhost otherhost.cfengine.com";

 # etc



 "i" slist => getindices("names");

insert_lines:

 "$(i)     $(names[$(i)])";

}


Next: , Previous: Set up name resolution, Up: Common high level issues

2.15 Set up sudo

Setting up sudo is straightforward, and is best managed by copying trusted files from a repository.

bundle agent system_files
{
vars:

 "masterfiles" string => "/subversion_projects/masterfiles";

# ...



files:

   "/etc/sudoers"

     comment => "Make sure the sudo configuration is secure and up to date",
       perms => mog("440","root","root"),
   copy_from => secure_cp("$(masterfiles)/sudoers","$(policy_server)");

}


Next: , Previous: Set up sudo, Up: Common high level issues

2.16 Set up a web server

Adapt this template to your operating system by adding multiple classes. Each web server runs something like the present module, which is entered into the bundlesequence like this:

#####################################################
#

# Apache webserver module

#

#####################################################



bundle agent web_server(state)
{
vars:

  "document_root" string => "/";

 ####################################################

 # Site specific configuration - put it in this file

 ####################################################



  "site_http_conf" string => "/home/mark/cfengine-inputs/httpd.conf";

 ####################################################

 # Software base

 ####################################################



  "match_package" slist => {
                           "apache2",
                           "apache2-mod_php5",
                           "apache2-prefork",
                           "php5"
                           };

 #########################################################



processes:

  web_ok.on::

   "apache2"

     restart_class => "start_apache";

  off::

   "apache2"

     process_stop => "/etc/init.d/apache2 stop";


 #########################################################



commands:

 start_apache::

   "/etc/init.d/apache2 start"; # or startssl

 #########################################################



packages:

  "$(match_package)"

     package_policy => "add",
     package_method => zypper,
     classes => if_ok("software_ok");

 #########################################################



files:

 software_ok::

  "/etc/sysconfig/apache2"

     edit_line => fixapache,
     classes => if_ok("web_ok");

 #########################################################



reports:

 !software_ok.on::

    "The web server software could not be installed";

 #########################################################



classes:

  "on"  expression => strcmp("$(state)","on");
  "off" expression => strcmp("$(state)","off");
}

#######################################################

# For the library

#######################################################



bundle edit_line fixapache

{
vars:

 "add_modules"     slist => {
                            "ssl",
                            "php5"
                            };

 "del_modules"     slist => {
                            "php3",
                            "php4",
                            "jk"
                            };

insert_lines:

 "APACHE_CONF_INCLUDE_FILES=\"$(web_server.site_http_conf)\"";

field_edits:

 #####################################################################

 # APACHE_MODULES="actions alias ssl php5 dav_svn authz_default jk" etc..

 #####################################################################



   "APACHE_MODULES=.*"

      # Insert module "columns" between the quoted RHS

      # using space separators



      edit_field => quotedvar("$(add_modules)","append");

   "APACHE_MODULES=.*"

      # Delete module "columns" between the quoted RHS

      # using space separators



      edit_field => quotedvar("$(del_modules)","delete");

   # if this line already exists, edit it



}


Previous: Set up a web server, Up: Common high level issues

2.17 Templating

With CFEngine you have a choice between editing `deltas' into files or distributing more-or-less finished templates. Which method you should choose depends should be made by whatever is easiest.

Example template:
#
# System file X

#



MYVARIABLE = something or other
HOSTNAME = $(sys.host)           # CFEngine fills this in

# ...



To copy and expand this template, you can use a pattern like this:
bundle agent test
{
methods:

 "any" usebundle => get_template("/etc/sudoers","400");
 "any" usebundle => get_template("/etc/hosts","644");

}

The the following driving code (based on `copy then edit') can be placed in a library, after configuring to your environmental locations:
bundle agent get_template(final_destination,mode)
{
vars:

 # This needs to ne preconfigured to your site



 "masterfiles"   string => "/home/mark/tmp";
 "this_template" string => lastnode("$(final_destination)","/");

files:

  "$(final_destination).staging"

       comment => "Get template and expand variables for this host",
         perms => mo("400","root"),
     copy_from => remote_cp("$(masterfiles)/templates/$(this_template)","$(policy_server)"),
        action => if_elapsed("60");

  "$(final_destination)"

       comment => "Expand the template",
        create => "true",
     edit_line => expand_template("$(final_destination).staging"),
 edit_defaults => empty,
         perms => mo("$(mode)","root"),
        action => if_elapsed("60");

}


Previous: Common high level issues, Up: Top

3 Common low level issues


Next: , Previous: Common low level issues, Up: Common low level issues

3.1 Add lines to a file

There are numerous approaches to adding lines to a file. Often the order of a configuration file is unimportant, we just need to ensure settings within it. A simple way of adding lines is show below.

body common control

{
any::

  bundlesequence  => { "insert" };
}

#######################################################



bundle agent insert

{
vars:

  "lines" string => 
                "
                One potato
                Two potato
                Three potatoe
                Four
                ";

files:

  "/tmp/test_insert"

            create => "true",
         edit_line => append_if_no_line("$(insert.lines)");

}

Also you could write this using a list variable:
body common control

{
any::

  bundlesequence  => { "insert" };
}

#######################################################



bundle agent insert

{
vars:

  "lines" slist => { "One potato", "Two potato",
                "Three potatoe", "Four" };

files:

  "/tmp/test_insert"

            create => "true",
         edit_line => append_if_no_line("@(insert.lines)");

}


Next: , Previous: Add lines to a file, Up: Common low level issues

3.2 Add users to passwd and group

Add lines to the password file, and users to group if they are not already there.

body common control
{
bundlesequence => { "addpasswd" };
inputs => { "cf_std_library.cf" };
}

bundle agent addpasswd
{
vars:

  # want to set these values by the names of their array keys



  "pwd[mark]" string => "mark:x:1000:100:Mark Burgess:/home/mark:/bin/bash";
  "pwd[fred]" string => "fred:x:1001:100:Right Said:/home/fred:/bin/bash";
  "pwd[jane]" string => "jane:x:1002:100:Jane Doe:/home/jane:/bin/bash";

  "users" slist => getindices("pwd");

files:

  "/etc/passwd"

        create => "true",
     edit_line => append_users_starting("addpasswd.pwd");

  "/etc/group"

       edit_line => append_user_field("users","4","@(addpasswd.users)");

}


Next: , Previous: Add users to passwd and group, Up: Common low level issues

3.3 Add software packages to the system

#
# Package managment

#



body common control
{
bundlesequence => { "packages" };
}

#############################################



bundle agent packages
{
vars:

 "match_package" slist => {
                          "apache2",
                          "apache2-mod_php5",
                          "apache2-prefork",
                          "php5"
                          };
packages:

 solaris::

  "$(match_package)"

     package_policy => "add",
     package_method => solaris;

 redhat|SuSE::

  "$(match_package)"

     package_policy => "add",
     package_method => yum;

}

Note you can also arrange to hide all the differences between package managers on an OS basis, but since some OSs have multiple managers, this might not be 100 percent correct.


Next: , Previous: Add software packages to the system, Up: Common low level issues

3.4 Add variable definitions to a file e.g. /etc/system

body common control
{
bundlesequence => { "setvars" };
inputs => { "cf_std_library.cf" };
}


bundle agent setvars
{
vars:

  # want to set these values by the names of their array keys



  "rhs[lhs1]" string => " Mary had a little pig";
  "rhs[lhs2]" string => "Whose Fleece was white as snow";
  "rhs[lhs3]" string => "And everywhere that Mary went";

  # oops, now change pig -> lamb



files:

  "/tmp/system"

        create => "true",
     edit_line => set_variable_values("setvars.rhs");

}

Results in:

lhs1= Mary had a little pig
lhs2=Whose Fleece was white as snow
lhs3=And everywhere that Mary went

An example of this would be to add variables to /etc/sysctl.conf on Linux:

body common control
{
bundlesequence => { "setvars" };
inputs => { "cf_std_library.cf" };
}


bundle agent setvars
{
vars:

  # want to set these values by the names of their array keys



  "rhs[net/ipv4/tcp_syncookies]" string => "1";
  "rhs[net/ipv4/icmp_echo_ignore_broadcasts]" string => "1";
  "rhs[net/ipv4/ip_forward]" string => "1";

  # oops, now change pig -> lamb



files:

  "/etc/sysctl"

        create => "true",
     edit_line => set_variable_values("setvars.rhs");

}


Next: , Previous: Add variable definitions to a file, Up: Common low level issues

3.5 Check file or directory permissions

bundle agent check_perms
{
vars:

  "ns_files" slist => {
                      "/local/iu/logs/admin",
                      "/local/iu/logs/security",
                      "/local/iu/logs/updates",
                      "/local/iu/logs/xfer"
                      };
files:

   NameServers::

    "/local/dns/pz"

            perms => mo("644","dns")
     depth_search => recurse("1"),
      file_select => exclude("secret_file");

    "/local/iu/dns/pz/FixSerial"

            perms => m("755"),
      file_select => plain;

    "$(ns_files)"

            perms => mo("644","dns"),
      file_select => plain;


    "$(ftp)/pub"
             perms => mog("644","root","other");

    "$(ftp)/pub"
             perms => m("644"),
      depth_search => recurse("inf");

    "$(ftp)/etc"        perms => mog("111","root","other");
    "$(ftp)/usr/bin/ls" perms => mog("111","root","other");
    "$(ftp)/dev"        perms => mog("555","root","other");
    "$(ftp)/usr"        perms => mog("555","root","other");
}


Next: , Previous: Check file or directory permissions, Up: Common low level issues

3.6 Copy files

files:

  "/var/cfengine/inputs"

    handle => "update_policy",
    perms => m("600"),
    copy_from => u_scp("$(master_location)",@(policy_server)),
    depth_search => recurse("inf"),
    file_select => input_files,
    action => immediate;

  "/var/cfengine/bin"

    perms => m("700"),
    copy_from => u_scp("/usr/local/sbin","localhost"),
    depth_search => recurse("inf"),
    file_select => cf3_files,
    action => immediate,
    classes => on_change("reload");


Next: , Previous: Copy files, Up: Common low level issues

3.7 Copy then edit a file convergently

To convergently chain a copy followed by edit, you need a staging file. First you copy to the staging file. Then you edit the final file and insert the staging file into it as part of the editing. This is convergent with respect to both stages of the process.

bundle agent master
{
files:

  "$(final_destination)"

         create => "true",
     edit_line => fix_file("$(staging_file)"),
 edit_defaults => empty,
         perms => mo("644","root"),
        action => ifelapsed("60");
}

#



bundle edit_line fix_file(f)
{
insert_lines:

  "$(f)"

     # insert this into an empty file to reconstruct



     insert_type => "file";

replace_patterns:

    "searchstring"

          replace_with => With("replacestring");
}



Next: , Previous: Copy then edit, Up: Common low level issues

3.8 Editing files

This is a huge topic. See also See Add lines to a file, See Editing tabular files, etc. Editing a file can be complex or simple, depending on needs.

Here is an example of how to comment out lines matching a number of patterns:

######################################################################
#

# Comment lines

#

######################################################################



body common control

{
version         =>   "1.2.3";
bundlesequence  => { "testbundle"  };
inputs          => { "cf_std_library.cf" };
}

########################################################



bundle agent testbundle

{
vars:

  "patterns" slist => { "finger.*", "echo.*", "exec.*", "rstat.*",
                                               "uucp.*", "talk.*" };

files:

  "/etc/inetd.conf"

      edit_line => comment_lines_matching("@(testbundle.patterns)","#");
}


Next: , Previous: Editing files, Up: Common low level issues

3.9 Editing tabular files

######################################################################

#

# File editing

#

# Normal ordering:

# - delete

# - replace | colum_edit

# - insert

#

######################################################################





body common control

{
version => "1.2.3";
bundlesequence  => { "testbundle"  };
}

########################################################



bundle agent testbundle

{
vars:

 "userset" slist => { "one-x", "two-x", "three-x" };

files:

  # Make a copy of the password file



  "/home/mark/tmp/passwd"

       create    => "true",
       edit_line => SetUserParam("mark","6","/set/this/shell");

  "/home/mark/tmp/group"

       create    => "true",
       edit_line => AppendUserParam("root","4","@(userset)");

commands:

  "/bin/echo" args => "$(userset)";

}

########################################################



bundle edit_line SetUserParam(user,field,val)
  {
  field_edits:

   "$(user):.*"

      # Set field of the file to parameter



      edit_field => col(":","$(field)","$(val)","set");
  }

########################################################



bundle edit_line AppendUserParam(user,field,allusers)
  {
  vars:

    "val" slist => { @(allusers) };

  field_edits:

   "$(user):.*"

      # Set field of the file to parameter



      edit_field => col(":","$(field)","$(val)","alphanum");

  }

########################################

# Bodies

########################################



body edit_field col(split,col,newval,method)

{
field_separator => "$(split)";
select_field    => "$(col)";
value_separator  => ",";
field_value     => "$(newval)";
field_operation => "$(method)";
extend_fields => "true";
}



Next: , Previous: Editing tabular files, Up: Common low level issues

3.10 Mount NFS filesystem

#####################################################################
# Mount NFS

#####################################################################



body common control

{
bundlesequence => { "mounts" };
}

#####################################################################



bundle agent mounts

{
storage:

  # Assumes the filesystem has been exported



  "/mnt" mount  => nfs("server.example.org","/home");
}

######################################################################



body mount nfs(server,source)

{
mount_type => "nfs";
mount_source => "$(source)";
mount_server => "$(server)";
edit_fstab => "true";
}


Next: , Previous: Mount NFS filesystem, Up: Common low level issues

3.11 Ordering promises

This counts to five by default. If we change ‘/bin/echo one’ to ‘/bin/echox one’, then the command will fail, causing us to skip five and go to six instead.

This shows how dependencies can be chained in spite of the order of promises in the bundle.

Normally the order of promises in a bundle is followed, within each promise type, and the types are ordered according to normal ordering.

####################################################
#

# Counting by the numbers...

#

####################################################



body common control

{
bundlesequence => { "order" };
}

####################################################



bundle agent order

{
vars:

 "list" slist => { "three", "four" };

commands:

 ok_later::

   "/bin/echo five";

 otherthing::

   "/bin/echo six";

 any::

  "/bin/echo one"    classes => d("ok_later","otherthing");
  "/bin/echo two";
  "/bin/echo $(list)";

 preserved_class::

  "/bin/echo seven";

}

############################################



body classes d(if,else)

{
promise_repaired => { "$(if)" };
repair_failed => { "$(else)" };
persist_time => "0";
}



Next: , Previous: Ordering promises, Up: Common low level issues

3.12 Set up a PXE boot server

Use cfengine to set up a PXE boot server.

#
# PXE boot server

#



bundle agent pxe
{
vars:

  "software" slist => {
                      "atftp",
                      "dhcp-server",
                      "syslinux",
                      "apache2"
                      };


   "dirs" slist => {
                   "/tftpboot",
                   "/tftpboot/cfengine/rpm",
                   "/tftpboot/cfengine/inputs",
                   "/tftpboot/pxelinux.cfg",
                   "/tftpboot/kickstart",
                   "/srv/www/repos"
                   };

   "tmp_location"    string => "/tftpboot/cfengine/inputs";

   # Distros that we can install



   "rh_distros" slist => { "4.7", "5.2" };
   "centos_distros" slist => { "5.2" };

   # File contents of atftp configuration



   "atftpd_conf" string => { "5.2" };
       "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promising state.      ###

###########################################



ATFTPD_OPTIONS=\"--daemon \"
ATFTPD_USE_INETD=\"no\"
ATFTPD_DIRECTORY=\"/tftpboot\"
ATFTPD_BIND_ADDRESSES=\"\"
       ";

   # File contents of DHCP configuration



   "dhcpd" string => { "5.2" };
       "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promising state.      ###

###########################################



DHCPD_INTERFACE=\"eth0\"
DHCPD_RUN_CHROOTED=\"yes\"
DHCPD_CONF_INCLUDE_FILES=\"\"
DHCPD_RUN_AS=\"dhcpd\"
DHCPD_OTHER_ARGS=\"\"
DHCPD_BINARY=\"\"
       ";

   "dhcpd_conf" string => { "5.2" };
       "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promising state.      ###

###########################################



allow booting;
allow bootp;
ddns-update-style none; ddns-updates off;
 subnet 192.168.0.0 netmask 255.255.255.0 {
   range 192.168.0.20 192.168.0.254;
   default-lease-time 3600;
   max-lease-time 4800;
   option routers 192.168.0.1;
   option domain-name \"test.cfengine.com\";
   option domain-name-servers 192.168.0.1;
   next-server 192.168.0.1;
   filename \"pxelinux.0\";
 }
 group {
   host node1 {
     # Dummy machine

     hardware ethernet 00:0F:1F:94:FE:07;
     fixed-address 192.168.0.11;
     option host-name \"node1\";
   }
   host node2 {
     # Dell Inspiron 1150

     hardware ethernet 00:0F:1F:0E:70:E7;
     fixed-address 192.168.0.12;
     option host-name \"node2\";
   }
 }
        ";

   # File contains of Apache2 HTTP configuration



   "httpd_conf" string => { "5.2" };
        "
# Repository for RHEL5

<Directory /srv/www/repos>
Options Indexes
AllowOverride None
</Directory>
Alias /repos /srv/www/repos

# PXE boot server

<Directory /tftpboot/distro/RHEL/5.2>
Options Indexes
AllowOverride None
</Directory>
Alias /distro/rhel/5.2 /tftpboot/distro/RHEL/5.2

<Directory /tftpboot/distro/RHEL/4.7>
Options Indexes
AllowOverride None
</Directory>
Alias /distro/rhel/4.7 /tftpboot/distro/RHEL/4.7

<Directory /tftpboot/distro/CentOS/5.2>
Options Indexes
AllowOverride None
</Directory>
Alias /distro/centos/5.2 /tftpboot/distro/CentOS/5.2

<Directory /tftpboot/kickstart>
Options Indexes
AllowOverride None
</Directory>
Alias /kickstart /tftpboot/kickstart

<Directory /tftpboot/cfengine>
Options Indexes
AllowOverride None
</Directory>
Alias /cfengine /tftpboot/cfengine
        ";

   # File contains of Kickstart for RHEL5 configuration



   "kickstart_rhel5_conf" string => { "5.2" };
        "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promissing state.     ###

###########################################



auth  --useshadow  --enablemd5
bootloader --location=mbr
clearpart --all --initlabel
graphical
firewall --disabled
firstboot --disable
key 77244a6377a8044a
keyboard no
lang en_US
logging --level=info
url --url=http://192.168.0.1/distro/rhel/5.2
network --bootproto=dhcp --device=eth0 --onboot=on
reboot
rootpw --iscrypted $1$eOnXdDPF$279sQ//zry6rnQktkATeM0
selinux --disabled
timezone --isUtc Europe/Oslo
install
part swap --bytes-per-inode=4096 --fstype=\"swap\" --recommended
part / --bytes-per-inode=4096 --fstype=\"ext3\" --grow --size=1

%packages
@core
@base
db4-devel
openssl-devel
gcc
flex
bison
libacl-devel
libselinux-devel
pcre-devel
#httpd

device-mapper-multipath
-sysreport

%post
cd /root
rpm -i http://192.168.0.1/cfengine/rpm/cfengine-3.0.1b1-1.el5.i386.rpm
#/sbin/chkconfig httpd on

cd /etc/yum.repos.d
wget http://192.168.0.1/repos/RHEL5.Base.repo
rpm --import /etc/pki/rpm-gpg/*
yum clean all
yum update
mkdir -p /root/cfengine_init
cd /root/cfengine_init
wget -nd -r http://192.168.0.1/cfengine/inputs/
/usr/local/sbin/cf-agent -B
/usr/local/sbin/cf-agent
        ";

   # File contains of PXElinux boot menu



   "pxelinux_boot_menu" string => { "5.2" };
        "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promissing state.     ###

###########################################



boot options:
     rhel5   - install 32 bit i386 RHEL 5.2             (MANUAL)
     rhel5w  - install 32 bit i386 RHEL 5.2             (AUTO)
     rhel4   - install 32 bit i386 RHEL 4.7 AS          (MANUAL)
     centos5 - install 32 bit i386 CentOS 5.2 (Desktop) (MANUAL)
        ";
   # File contains of PXElinux default configuration



   "pxelinux_default" string => { "5.2" };
        "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promissing state.     ###

###########################################



default rhel5
timeout 300
prompt 1
display pxelinux.cfg/boot.msg
F1 pxelinux.cfg/boot.msg

# install i386 RHEL 5.2

label rhel5
   kernel vmlinuz-RHEL5U2
   append initrd=initrd-RHEL5U2 load_ramdisk=1 ramdisk_size=16384 install=http://192.168.0.1/distro/rhel/5.2

# install i386 RHEL 5.2 using Kickstart

label rhel5w
   kernel vmlinuz-RHEL5U2
   append initrd=initrd-RHEL5U2 load_ramdisk=1 ramdisk_size=16384 ks=http://192.168.0.1/kickstart/kickstart-RHEL5U2.cfg

# install i386 RHEL 4.7

label rhel4
   kernel vmlinuz-RHEL4U7
   append initrd=initrd-RHEL4U7 load_ramdisk=1 ramdisk_size=16384 install=http://192.168.0.1/distro/rhel/4.7

# install i386 CentOS 5.2

label centos5
   kernel vmlinuz-CentOS5.2
   append initrd=initrd-CentOS5.2 load_ramdisk=1 ramdisk_size=16384 install=http://192.168.0.1/distro/centos/5.2
        ";

   # File contains of specified PXElinux default to be a RHEL5 webserver



   "pxelinux_rhel5_webserver" string => { "5.2" };
        "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promissing state.     ###

###########################################



# install i386 RHEL 5.2 using Kickstart

default rhel5w
label rhel5w
   kernel vmlinuz-RHEL5U2
   append initrd=initrd-RHEL5U2 load_ramdisk=1 ramdisk_size=16384 ks=http://192.168.0.1/kickstart/kickstart-RHEL5U2.cfg
        ";

   # File contains of a local repository for RHEL5



   "rhel5_base_repo" string => { "5.2" };
        "
###########################################

### This file is protected by cfengine. ###

### Whatever you do, it will be changed ###

###     back to a promissing state.     ###

###########################################



# Local Repository

[Server]
name=Server
baseurl=http://192.168.0.1/repos/rhel5/Server/
enable=1
[VT]
name=VT
baseurl=http://192.168.0.1/repos/rhel5/VT/
enable=1
[Cluster]
name=Cluster
baseurl=http://192.168.0.1/repos/rhel5/Cluster/
enable=1
[ClusterStorage]
name=Cluster Storage
baseurl=http://192.168.0.1/repos/rhel5/ClusterStorage/
enable=1
        ";
#####################################################





files:

 packages_ok::

  # Create files/dirs and edit the new files



  "/tftpboot/distro/RHEL/$(rh_distros)/."
       create => "true";

  "/tftpboot/distro/CentOS/$(centos_distros)/."
       create => "true";

  "$(dirs)/."
       create => "true";

  "/tftpboot/pxelinux.cfg/boot.msg"
       create => "true",
       perms => mo("644","root"),
       edit_line => append_if_no_line("$(pxelinux_boot_menu)"),
       edit_defaults => empty;

  "/tftpboot/pxelinux.cfg/default"
       create => "true",
       perms => mo("644","root"),
       edit_line => append_if_no_line("$(pxelinux_default)"),
       edit_defaults => empty;

  "/tftpboot/pxelinux.cfg/default.RHEL5.webserver"
       create => "true",
       perms => m("644","root"),
       edit_line => append_if_no_line("$(pxelinux_rhel5_webserver)"),
       edit_defaults => empty;

  "/tftpboot/kickstart/kickstart-RHEL5U2.cfg"
       create => "true",
       perms => m("644","root"),
       edit_line => append_if_no_line("$(kickstart_rhel5_conf)"),
       edit_defaults => empty;

  "/srv/www/repos/RHEL5.Base.repo"
       create => "true",
       perms => m("644","root"),
       edit_line => append_if_no_line("$(rhel5_base_repo)"),
       edit_defaults => empty;

  # Copy files



  "/tftpboot"

    copy_from => mycopy_local("/usr/share/syslinux","localhost"),
    depth_search => recurse("inf"),
    file_select => pxelinux_files,
    action => immediate;

  "$(tmp_location)"

    perms => m("644"),
    copy_from => mycopy("/var/cfengine/inputs","localhost"),
    depth_search => recurse("inf"),
    file_select => input_files,
    action => immediate;

  # Edit atftp, dhcp and apache2 configurations



  "/etc/sysconfig/atftpd"
     edit_line => append_if_no_line("$(atftpd_conf)"),
     edit_defaults => empty,
     classes => satisfied("atftpd_ready");

  "/etc/sysconfig/dhcpd"
     edit_line => append_if_no_line("$(dhcpd)"),
     edit_defaults => empty;

  "/etc/dhcpd.conf"
     edit_line => append_if_no_line("$(dhcpd_conf)"),
     edit_defaults => empty,
     classes => satisfied("dhcpd_ready");

  "/etc/apache2/httpd.conf"
     edit_line => append_if_no_line("$(httpd_conf)"),
     edit_defaults => def,
     classes => satisfied("apache2_ok");

  # Make a static link



  "/tftpboot/pxelinux.cfg/C0A8000C"
     link_from => mylink("/tftpboot/pxelinux.cfg/default.RHEL5.webserver");

  # Hash comment some lines for apaches



  apache2_ok::
  "/etc/apache2/httpd.conf"
     edit_line => comment_lines_matching_apache2("#"),
     classes => satisfied("apache2_ready");

commands:

  # Restart services

  atftpd_ready::
  "/etc/init.d/atftpd restart";

  dhcpd_ready::
  "/etc/init.d/dhcpd restart";

  apache2_ready::
  "/etc/init.d/apache2 restart";


#####################################################



packages:

 ipv4_192_168_0_1::
 # Only the PXE boot server



 "$(software)"

     package_policy => "add",
     package_method => zypper,
     classes => satisfied("packages_ok");

}

#####################################################

########### *** Bodies are here *** #################

#####################################################



body file_select pxelinux_files

{
leaf_name => { "pxelinux.0" };

file_result => "leaf_name";
}

#####################################################



body copy_from mycopy_local(from,server)

{
source      => "$(from)";
compare     => "digest";
}

#########################################################



body link_from mylink(x)
{
source => "$(x)";
link_type => "symlink";
}

#######################################################



body classes satisfied(new_class)

{
promise_kept => { "$(new_class)"};
promise_repaired => { "$(new_class)"};
}

#######################################################



bundle edit_line comment_lines_matching_apache2(comment)
  {

  vars:
   "regex" slist => { "\s.*Options\sNone", "\s.*AllowOverride\sNone", "\s.*Deny\sfrom\sall" };

  replace_patterns:

   "^($(regex))$"
      replace_with => comment("$(comment)");
  }

#######################################################




Next: , Previous: Set up a PXE boot server, Up: Common low level issues

3.13 Tidying garbage files

Emulating the `tidy' feature of cfengine 2.

#######################################################
#

# Deleting files, like cf2 tidy age=0 r=inf

#

#######################################################



body common control

{
 any::

  bundlesequence  => { "testbundle" };
}

############################################



bundle agent testbundle

{
files:

  "/tmp/test"

    delete => tidyfiles,
    file_select => zero_age,
    depth_search => recurse("inf");
}

#########################################################



body depth_search recurse(d)

{
#include_basedir => "true";

depth => "$(d)";
}

#########################################################



body delete tidy

{
dirlinks => "delete";
rmdirs   => "false"; 
}

#########################################################



body file_select zero_age

#

# we can build old "include", "exclude", and "ignore"

# from these as standard patterns - these bodies can

# form a library of standard patterns

#



{
mtime     => irange(ago(1,0,0,0,0,0),now);
file_result => "mtime"; 
}


Next: , Previous: Tidying garbage files, Up: Common low level issues

3.14 Unmount NFS filesystem

#####################################################################
# Mount NFS

#####################################################################



body common control

{
bundlesequence => { "mounts" };
}

#####################################################################



bundle agent mounts

{
storage:

  # Assumes the filesystem has been exported



  "/mnt" mount  => nfs("server.example.org","/home");
}

######################################################################



body mount nfs(server,source)

{
mount_type => "nfs";
mount_source => "$(source)";
mount_server => "$(server)";
edit_fstab => "true";
unmount => "true";
}


Previous: Unmount NFS filesystem, Up: Common low level issues

3.15 Web server modules

The problem of editing the correct modules into the list of standard modules for the Apache web server. This example is based on the standard configuration deployment of SuSE Linux. Simply provide the list of modules you want and another list that you don't want.

#######################################################
#

# Apache 2 reconfig - modelled on SuSE

#

#######################################################



body common control

{
any::

  bundlesequence  => {
                     apache
                     };
}

#######################################################



bundle agent apache

{
files:

 SuSE::

  "/etc/sysconfig/apache2"

     edit_line => fixapache;
}

#######################################################

# For the library

#######################################################



bundle edit_line fixapache

{
vars:

 "add_modules"     slist => {
                            "dav",
                            "dav_fs",
                            "ssl",
                            "php5",
                            "dav_svn",
                            "xyz",
                            "superduper"
                            };

 "del_modules"     slist => {
                            "php3",
                            "jk",
                            "userdir",
                            "imagemap",
                            "alias"
                            };

insert_lines:

 "APACHE_CONF_INCLUDE_FILES=\"/site/masterfiles/local-http.conf\"";

field_edits:

 #####################################################################

 # APACHE_MODULES="authz_host actions alias ..."

 #####################################################################



    # Values have the form NAME = "quoted space separated list"



   "APACHE_MODULES=.*"

      # Insert module "columns" between the quoted RHS

      # using space separators



      edit_field => quotedvar("$(add_modules)","append");

   "APACHE_MODULES=.*"

      # Delte module "columns" between the quoted RHS

      # using space separators



      edit_field => quotedvar("$(del_modules)","delete");

   # if this line already exists, edit it



}

Table of Contents


Footnotes

[1] In cfengine 2 there is a separate action type for configuring the system resolver. In cfengine 3 this has been deprecated for this standard method using the basic functionality of cfengine.