See the files promises and edit_line bundles documentation for a comprehensive reference on the bundles, body types, and attributes used here.

file bodies

control

Prototype: control

Description: Include policy files used by this policy file as part of inputs

Implementation:

code
body file control
{
      inputs => { @(files_common.inputs) };
}

edit_field bodies

fstab_options

Prototype: fstab_options(newval, method)

Description: Edit the options field in a fstab format

Arguments:

This body edits the options field in the fstab file format. The method is a field_operation which can be append, prepend, set, delete, or alphanum. The newval option is OS-specific.

Example:

code
  # from the `fstab_options_editor`
  field_edits:
     "(?!#)\S+\s+$(mount)\s.+"
     edit_field => fstab_options($(option), $(method));

Implementation:

code
body edit_field fstab_options(newval, method)
{
      field_separator => "\s+";
      select_field    => "4";
      value_separator  => ",";
      field_value     => "$(newval)";
      field_operation => "$(method)";
}

quoted_var

Prototype: quoted_var(newval, method)

Description: Edit the quoted value of the matching line

Arguments:

  • newval: The new value
  • method: The method by which to edit the field

Implementation:

code
body edit_field quoted_var(newval,method)
{
      field_separator => "\"";
      select_field    => "2";
      value_separator  => " ";
      field_value     => "$(newval)";
      field_operation => "$(method)";
      extend_fields => "false";
      allow_blank_fields => "true";
}

col

Prototype: col(split, col, newval, method)

Description: Edit tabluar data with comma-separated sub-values

Arguments:

  • split: The separator that defines columns
  • col: The (1-based) index of the value to change
  • newval: The new value
  • method: The method by which to edit the field

Implementation:

code
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";
      allow_blank_fields => "true";
}

line

Prototype: line(split, col, newval, method)

Description: Edit tabular data with space-separated sub-values

Arguments:

  • split: The separator that defines columns
  • col: The (1-based) index of the value to change
  • newval: The new value
  • method: The method by which to edit the field

Implementation:

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

replace_with bodies

text_between_match1_and_match2

Prototype: text_between_match1_and_match2(_text)

Description: Replace matched line with substituted string

Arguments:

  • _text: String to substitute between first and second match

Implementation:

code
body replace_with text_between_match1_and_match2( _text )
{
        replace_value => "$(match.1)$(_text)$(match.2)";
        occurrences => "all";
}

value

Prototype: value(x)

Description: Replace matching lines

Arguments:

  • x: The replacement string

Implementation:

code
body replace_with value(x)
{
      replace_value => "$(x)";
      occurrences => "all";
}

select_region bodies

INI_section

Prototype: INI_section(x)

Description: Restrict the edit_line promise to the lines in section [x]

Arguments:

  • x: The name of the section in an INI-like configuration file

Implementation:

code
body select_region INI_section(x)
{
      select_start => "\[$(x)\]\s*";
      select_end => "\[.*\]\s*";

@if minimum_version(3.10)
      select_end_match_eof => "true";
@endif
}

edit_defaults bodies

std_defs

Prototype: std_defs

Description: Standard definitions for edit_defaults Don't empty the file before editing starts and don't make a backup.

Implementation:

code
body edit_defaults std_defs
{
      empty_file_before_editing => "false";
      edit_backup => "false";
      #max_file_size => "300000";
}

empty

Prototype: empty

Description: Empty the file before editing

No backup is made

Implementation:

code
body edit_defaults empty
{
      empty_file_before_editing => "true";
      edit_backup => "false";
      #max_file_size => "300000";
}

no_backup

Prototype: no_backup

Description: Don't make a backup of the file before editing

Implementation:

code
body edit_defaults no_backup
{
      edit_backup => "false";
}

backup_timestamp

Prototype: backup_timestamp

Description: Make a timestamped backup of the file before editing

Implementation:

code
body edit_defaults backup_timestamp
{
      empty_file_before_editing => "false";
      edit_backup => "timestamp";
      #max_file_size => "300000";
}

location bodies

start

Prototype: start

Description: Editing occurs before the matched line

Implementation:

code
body location start
{
      before_after => "before";
}

after

Prototype: after(str)

Description: Editing occurs after the line matching str

Arguments:

  • str: Regular expression matching the file line location

Implementation:

code
body location after(str)
{
      before_after => "after";
      select_line_matching => "$(str)";
}

before

Prototype: before(str)

Description: Editing occurs before the line matching str

Arguments:

  • str: Regular expression matching the file line location

Implementation:

code
body location before(str)
{
      before_after => "before";
      select_line_matching => "$(str)";
}

replace_with bodies

comment

Prototype: comment(c)

Description: Comment all lines matching the pattern by preprending c

Arguments:

  • c: The prefix that comments out lines

Implementation:

code
body replace_with comment(c)
{
      replace_value => "$(c) $(match.1)";
      occurrences => "all";
}

uncomment

Prototype: uncomment

Description: Uncomment all lines matching the pattern by removing anything outside the matching string

Implementation:

code
body replace_with uncomment
{
      replace_value => "$(match.1)";
      occurrences => "all";
}

copy_from bodies

secure_cp

Prototype: secure_cp(from, server)

Description: Download a file from a remote server over an encrypted channel

Only copy the file if it is different from the local copy, and verify that the copy is correct.

Arguments:

  • from: The location of the file on the remote server
  • server: The hostname or IP of the server from which to download

Implementation:

code
body copy_from secure_cp(from,server)
{
      source      => "$(from)";
      servers     => { "$(server)" };
      compare     => "digest";
      encrypt     => "true";
      verify      => "true";
}

remote_cp

Prototype: remote_cp(from, server)

Description: Download a file from a remote server.

Arguments:

  • from: The location of the file on the remote server
  • server: The hostname or IP of the server from which to download

Implementation:

code
body copy_from remote_cp(from,server)
{
      servers     => { "$(server)" };
      source      => "$(from)";
      compare     => "mtime";
}

remote_dcp

Prototype: remote_dcp(from, server)

Description: Download a file from a remote server if it is different from the local copy.

Arguments:

  • from: The location of the file on the remote server
  • server: The hostname or IP of the server from which to download

See Also: local_dcp()

Implementation:

code
body copy_from remote_dcp(from,server)
{
      servers     => { "$(server)" };
      source      => "$(from)";
      compare     => "digest";
}

local_cp

Prototype: local_cp(from)

Description: Copy a file if the modification time or creation time of the source file is newer (the default comparison mechanism).

Arguments:

  • from: The path to the source file.

Example:

code
bundle agent example
{
  files:
      "/tmp/file.bak"
        copy_from => local_cp("/tmp/file");
}

See Also: local_dcp()

Implementation:

code
body copy_from local_cp(from)
{
      source      => "$(from)";
}

local_dcp

Prototype: local_dcp(from)

Description: Copy a local file if the hash on the source file differs.

Arguments:

  • from: The path to the source file.

Example:

code
bundle agent example
{
  files:
      "/tmp/file.bak"
      copy_from => local_dcp("/tmp/file");
}

See Also: local_cp(), remote_dcp()

Implementation:

code
body copy_from local_dcp(from)
{
      source      => "$(from)";
      compare     => "digest";
}

perms_cp

Prototype: perms_cp(from)

Description: Copy a local file and preserve file permissions on the local copy.

Arguments:

  • from: The path to the source file.

Implementation:

code
body copy_from perms_cp(from)
{
      source      => "$(from)";
      preserve    => "true";
}

perms_dcp

Prototype: perms_dcp(from)

Description: Copy a local file if it is different from the existing copy and preserve file permissions on the local copy.

Arguments:

  • from: The path to the source file.

Implementation:

code
body copy_from perms_dcp(from)
{
      source      => "$(from)";
      preserve    => "true";
      compare     => "digest";
}

backup_local_cp

Prototype: backup_local_cp(from)

Description: Copy a local file and keep a backup of old versions.

Arguments:

  • from: The path to the source file.

Implementation:

code
body copy_from backup_local_cp(from)
{
      source      => "$(from)";
      copy_backup => "timestamp";
}

seed_cp

Prototype: seed_cp(from)

Description: Copy a local file if the file does not already exist, i.e. seed the placement

Arguments:

  • from: The path to the source file.

Example:

code
bundle agent home_dir_init
{
  files:
      "/home/mark.burgess/."
      copy_from => seed_cp("/etc/skel"),
      depth_search => recurse(inf),
      file_select => all,
      comment => "We want to be sure that the home directory has files that are
                  present in the skeleton.";
}

Implementation:

code
body copy_from seed_cp(from)
{
      source      => "$(from)";
      compare     => "exists";
}

sync_cp

Prototype: sync_cp(from, server)

Description: Synchronize a file with a remote server.

  • If the file does not exist on the remote server then it should be purged.
  • Allow types to change (directories to files and vice versa).
  • The mode of the remote file should be preserved.
  • Files are compared using the default comparison (mtime or ctime).

Arguments:

  • from: The location of the file on the remote server
  • server: The hostname or IP of the server from which to download

Example:

code
files:
  "/tmp/masterfiles/."
    copy_from => sync_cp( "/var/cfengine/masterfiles", $(sys.policy_server) ),
    depth_search => recurse(inf),
    file_select => all,
    comment => "Mirror masterfiles from the hub to a temporary directory";

See Also: dir_sync(), copyfrom_sync()

Implementation:

code
body copy_from sync_cp(from,server)
{
      servers     => { "$(server)" };
      source      => "$(from)";
      purge       => "true";
      preserve    => "true";
      type_check  => "false";
}

no_backup_cp

Prototype: no_backup_cp(from)

Description: Copy a local file and don't make any backup of the previous version

Arguments:

  • from: The path to the source file.

Implementation:

code
body copy_from no_backup_cp(from)
{
      source      => "$(from)";
      copy_backup => "false";
}

no_backup_dcp

Prototype: no_backup_dcp(from)

Description: Copy a local file if contents have changed, and don't make any backup of the previous version

Arguments:

  • from: The path to the source file.

Implementation:

code
body copy_from no_backup_dcp(from)
{
      source      => "$(from)";
      copy_backup => "false";
      compare     => "digest";
}

no_backup_rcp

Prototype: no_backup_rcp(from, server)

Description: Download a file if it's newer than the local copy, and don't make any backup of the previous version

Arguments:

  • from: The location of the file on the remote server
  • server: The hostname or IP of the server from which to download

Implementation:

code
body copy_from no_backup_rcp(from,server)
{
      servers     => { "$(server)" };
      source      => "$(from)";
      compare     => "mtime";
      copy_backup => "false";
}

ln_s

Prototype: ln_s(x)

Description: Create a symbolink link to x The link is created even if the source of the link does not exist.

Arguments:

  • x: The source of the link

Implementation:

code
body link_from ln_s(x)
{
      link_type => "symlink";
      source => "$(x)";
      when_no_source => "force";
}

linkchildren

Prototype: linkchildren(tofile)

Description: Create a symbolink link to tofile If the promiser is a directory, children are linked to the source, unless entries with identical names already exist. The link is created even if the source of the link does not exist.

Arguments:

  • tofile: The source of the link

Implementation:

code
body link_from linkchildren(tofile)
{
      source        => "$(tofile)";
      link_type     => "symlink";
      when_no_source  => "force";
      link_children => "true";
      when_linking_children => "if_no_such_file"; # "override_file";
}

linkfrom

Prototype: linkfrom(source, type)

Description: Make any kind of link to a file

Arguments:

  • source: link to this
  • type: the link's type (symlink or hardlink)

Implementation:

code
body link_from linkfrom(source, type)
{
      source => $(source);
      link_type => $(type);
}

perms bodies

m

Prototype: m(mode)

Description: Set the file mode

Arguments:

  • mode: The new mode

Implementation:

code
body perms m(mode)
{
      mode   => "$(mode)";

#+begin_ENT-951
# Remove after 3.20 is not supported
        rxdirs => "true";
@if minimum_version(3.20)
        rxdirs => "false";
@endif
#+end
}

mo

Prototype: mo(mode, user)

Description: Set the file's mode and owners

Arguments:

  • mode: The new mode
  • user: The username of the new owner

Implementation:

code
body perms mo(mode,user)
{
      owners => { "$(user)" };
      mode   => "$(mode)";

#+begin_ENT-951
# Remove after 3.20 is not supported
        rxdirs => "true";
@if minimum_version(3.20)
        rxdirs => "false";
@endif
#+end
}

mog

Prototype: mog(mode, user, group)

Description: Set the file's mode, owner and group

Arguments:

  • mode: The new mode
  • user: The username of the new owner
  • group: The group name

Implementation:

code
body perms mog(mode,user,group)
{
      owners => { "$(user)" };
      groups => { "$(group)" };
      mode   => "$(mode)";

#+begin_ENT-951
# Remove after 3.20 is not supported
        rxdirs => "true";
@if minimum_version(3.20)
        rxdirs => "false";
@endif
#+end
}

og

Prototype: og(u, g)

Description: Set the file's owner and group

Arguments:

  • u: The username of the new owner
  • g: The group name

Implementation:

code
body perms og(u,g)
{
      owners => { "$(u)" };
      groups => { "$(g)" };
}

owner

Prototype: owner(user)

Description: Set the file's owner

Arguments:

  • user: The username of the new owner

Implementation:

code
body perms owner(user)
{
      owners => { "$(user)" };
}

system_owned

Prototype: system_owned(mode)

Description: Set the file owner and group to the system default

Arguments:

  • mode: the access permission in octal format

Example:

code
files:
    "/etc/passwd" perms => system_owned("0644");

Implementation:

code
body perms system_owned(mode)
{
      mode   => "$(mode)";

#+begin_ENT-951
# Remove after 3.20 is not supported
        rxdirs => "true";
@if minimum_version(3.20)
        rxdirs => "false";
@endif
#+end

    !windows::
        owners => { "root" };

    windows::

      # NOTE: Setting owners will generate an error if the policy is not being
      # executed as the user who's ownership is being targeted. While it seems
      # that should typically be Administrator or SYSTEM, both are reported to
      # result in errors by users, thus owners is currently omitted for Windows.

      # ENT-9778
      groups => { "Administrators" };

    freebsd|openbsd|netbsd|darwin::
      groups => { "wheel" };

    linux::
      groups => { "root" };

    solaris::
      groups => { "sys" };

    aix::
      groups => { "system" };
}

acl bodies

access_generic

Prototype: access_generic(acl)

Description: Set the aces of the access control as specified

Default/inherited ACLs are left unchanged. This body is applicable for both files and directories on all platforms.

Arguments:

  • acl: The aces to be set

Implementation:

code
body acl access_generic(acl)
{
      acl_method => "overwrite";
      aces => { "@(acl)" };

    windows::
      acl_type => "ntfs";

    !windows::
      acl_type => "posix";
}

ntfs

Prototype: ntfs(acl)

Description: Set the aces on NTFS file systems, and overwrite existing ACLs.

This body requires CFEngine Enterprise.

Arguments:

  • acl: The aces to be set

Implementation:

code
body acl ntfs(acl)
{
      acl_type => "ntfs";
      acl_method => "overwrite";
      aces => { "@(acl)" };
}

strict

Prototype: strict

Description: Limit file access via ACLs to users with administrator privileges, overwriting existing ACLs.

Note: May need to take ownership of file/dir to be sure no-one else is allowed access.

Implementation:

code
body acl strict
{
      acl_method => "overwrite";

    windows::
      aces => { "user:Administrator:rwx" };
    !windows::
      aces => { "user:root:rwx" };
}

depth_search bodies

recurse

Prototype: recurse(d)

Description: Search files and direcories recursively, up to the specified depth Directories on different devices are excluded.

Arguments:

  • d: The maximum search depth

Implementation:

code
body depth_search recurse(d)
{
      depth => "$(d)";
      xdev  => "true";
}

recurse_ignore

Prototype: recurse_ignore(d, list)

Description: Search files and directories recursively, but don't recurse into the specified directories

Arguments:

  • d: The maximum search depth
  • list: The list of directories to be excluded

Implementation:

code
body depth_search recurse_ignore(d,list)
{
      depth => "$(d)";
      exclude_dirs => { @(list) };
}

include_base

Prototype: include_base

Description: Search files and directories recursively, starting from the base directory.

Implementation:

code
body depth_search include_base
{
      include_basedir => "true";
}

recurse_with_base

Prototype: recurse_with_base(d)

Description: Search files and directories recursively up to the specified depth, starting from the base directory excluding directories on other devices.

Arguments:

  • d: The maximum search depth

Implementation:

code
body depth_search recurse_with_base(d)
{
      depth => "$(d)";
      xdev  => "true";
      include_basedir => "true";
}

delete bodies

tidy

Prototype: tidy

Description: Delete the file and remove empty directories and links to directories

Implementation:

code
body delete tidy
{
      dirlinks => "delete";
      rmdirs   => "true";
}

rename bodies

disable

Prototype: disable

Description: Disable the file

Implementation:

code
body rename disable
{
      disable => "true";
}

rotate

Prototype: rotate(level)

Description: Rotate and store up to level backups of the file

Arguments:

  • level: The number of backups to store

Implementation:

code
body rename rotate(level)
{
      rotate => "$(level)";
}

to

Prototype: to(file)

Description: Rename the file to file

Arguments:

  • file: The new name of the file

Implementation:

code
body rename to(file)
{
      newname => "$(file)";
}

file_select bodies

name_age

Prototype: name_age(name, days)

Description: Select files that have a matching name and have not been modified for at least days

Arguments:

  • name: A regex that matches the file name
  • days: Number of days

Implementation:

code
body file_select name_age(name,days)
{
      leaf_name   => { "$(name)" };
      mtime       => irange(0,ago(0,0,"$(days)",0,0,0));
      file_result => "mtime.leaf_name";
}

days_old

Prototype: days_old(days)

Description: Select files that have not been modified for at least days

Arguments:

  • days: Number of days

Implementation:

code
body file_select days_old(days)
{
      mtime       => irange(0,ago(0,0,"$(days)",0,0,0));
      file_result => "mtime";
}

size_range

Prototype: size_range(from, to)

Description: Select files that have a size within the specified range

Arguments:

  • from: The lower bound of the allowed file size
  • to: The upper bound of the allowed file size

Implementation:

code
body file_select size_range(from,to)
{
      search_size => irange("$(from)","$(to)");
      file_result => "size";
}

bigger_than

Prototype: bigger_than(size)

Description: Select files that are above a given size

Arguments:

  • size: The number of bytes files have

Implementation:

code
body file_select bigger_than(size)
{
      search_size => irange("0","$(size)");
      file_result => "!size";
}

exclude

Prototype: exclude(name)

Description: Select all files except those that match name

Arguments:

  • name: A regular expression

Implementation:

code
body file_select exclude(name)
{
      leaf_name  => { "$(name)"};
      file_result => "!leaf_name";
}

not_dir

Prototype: not_dir

Description: Select all files that are not directories

Implementation:

code
body file_select not_dir
{
      file_types => { "dir" };
      file_result => "!file_types";
}

plain

Prototype: plain

Description: Select plain, regular files

Implementation:

code
body file_select plain
{
      file_types  => { "plain" };
      file_result => "file_types";
}

dirs

Prototype: dirs

Description: Select directories

Implementation:

code
body file_select dirs
{
      file_types  => { "dir" };
      file_result => "file_types";
}

by_name

Prototype: by_name(names)

Description: Select files that match names

Arguments:

  • names: A regular expression

Implementation:

code
body file_select by_name(names)
{
      leaf_name  => { @(names)};
      file_result => "leaf_name";
}

ex_list

Prototype: ex_list(names)

Description: Select all files except those that match names

Arguments:

  • names: A list of regular expressions

Implementation:

code
body file_select ex_list(names)
{
      leaf_name  => { @(names) };
      file_result => "!leaf_name";
}

all

Prototype: all

Description: Select all file system entries

Implementation:

code
body file_select all
{
      leaf_name => { ".*" };
      file_result => "leaf_name";
}

older_than

Prototype: older_than(years, months, days, hours, minutes, seconds)

Description: Select files older than the date-time specified

Arguments:

  • years: Number of years
  • months: Number of months
  • days: Number of days
  • hours: Number of hours
  • minutes: Number of minutes
  • seconds: Number of seconds

Generic older_than selection body, aimed to have a common definition handy for every case possible.

Implementation:

code
body file_select older_than(years, months, days, hours, minutes, seconds)
{
      mtime       => irange(0,ago("$(years)","$(months)","$(days)","$(hours)","$(minutes)","$(seconds)"));
      file_result => "mtime";
}

filetype_older_than

Prototype: filetype_older_than(filetype, days)

Description: Select files of specified type older than specified number of days

Arguments:

  • filetype: File type to select
  • days: Number of days

This body only takes a single filetype, see filetypes_older_than() if you want to select more than one type of file.

Implementation:

code
body file_select filetype_older_than(filetype, days)
{
      file_types => { "$(filetype)" };
      mtime      => irange(0,ago(0,0,"$(days)",0,0,0));
      file_result => "file_types.mtime";
}

filetypes_older_than

Prototype: filetypes_older_than(filetypes, days)

Description: Select files of specified types older than specified number of days

This body only takes a list of filetypes

Arguments:

  • filetypes: A list of file types
  • days: Number of days

See also: filetype_older_than()

Implementation:

code
body file_select filetypes_older_than(filetypes, days)
{
      file_types => { @(filetypes) };
      mtime      => irange(0,ago(0,0,"$(days)",0,0,0));
      file_result => "file_types.mtime";
}

symlinked_to

Prototype: symlinked_to(target)

Description: Select symlinks that point to $(target)

Arguments:

  • target: The file the symlink should point to in order to be selected

Implementation:

code
body file_select symlinked_to(target)
{
  file_types  => { "symlink" };
  issymlinkto => { "$(target)" };
  file_result => "issymlinkto";
}

changes bodies

detect_all_change

Prototype: detect_all_change

Description: Detect all file changes using the best hash method

This is fierce, and will cost disk cycles

Implementation:

code
body changes detect_all_change
{
      hash           => "best";
      report_changes => "all";
      update_hashes  => "yes";
}

detect_all_change_using

Prototype: detect_all_change_using(hash)

Description: Detect all file changes using a given hash method

Detect all changes using a configurable hashing algorithm for times when you care about both content and file stats e.g. mtime

Arguments:

  • hash: supported hashing algorithm (md5, sha1, sha224, sha256, sha384, sha512, best)

Implementation:

code
body changes detect_all_change_using(hash)
{
      hash           => "$(hash)";
      report_changes => "all";
      update_hashes  => "yes";
}

detect_content

Prototype: detect_content

Description: Detect file content changes using md5

This is a cheaper alternative

Implementation:

code
body changes detect_content
{
      hash           => "md5";
      report_changes => "content";
      update_hashes  => "yes";
}

detect_content_using

Prototype: detect_content_using(hash)

Description: Detect file content changes using a given hash algorithm.

For times when you only care about content, not file stats e.g. mtime

Arguments:

  • hash: - supported hashing algorithm (md5, sha1, sha224, sha256, sha384, sha512, best)

Implementation:

code
body changes detect_content_using(hash)
{
      hash           => "$(hash)";
      report_changes => "content";
      update_hashes  => "yes";
}

noupdate

Prototype: noupdate

Description: Detect content changes in (small) files that should never change

Implementation:

code
body changes noupdate
{
      hash           => "sha256";
      report_changes => "content";
      update_hashes  => "no";
}

diff

Prototype: diff

Description: Detect file content changes using sha256 and report the diff to CFEngine Enterprise

Implementation:

code
body changes diff
{
      hash           => "sha256";
      report_changes => "content";
      report_diffs   => "true";
      update_hashes  => "yes";
}

all_changes

Prototype: all_changes

Description: Detect all file changes using sha256 and report the diff to CFEngine Enterprise

Implementation:

code
body changes all_changes
{
      hash           => "sha256";
      report_changes => "all";
      report_diffs   => "true";
      update_hashes  => "yes";
}

diff_noupdate

Prototype: diff_noupdate

Description: Detect content changes in (small) files and report the diff to CFEngine Enterprise

Implementation:

code
body changes diff_noupdate
{
      hash           => "sha256";
      report_changes => "content";
      report_diffs   => "true";
      update_hashes  => "no";
}

copy_from bodies

copyfrom_sync

Prototype: copyfrom_sync(f)

Description: Copy a directory or file with digest checksums, preserving attributes and purging leftovers

Arguments:

  • f: the file or directory

Implementation:

code
body copy_from copyfrom_sync(f)
{
      source => "$(f)";
      purge => "true";
      preserve => "true";
      type_check => "false";
      compare => "digest";
}

common bodies

files_common

Prototype: files_common

Description: Enumerate policy files used by this policy file for inclusion to inputs

Implementation:

code
bundle common files_common
{
  vars:
      "inputs" slist => { "$(this.promise_dirname)/common.cf" };
}

edit_line bundles

insert_before_if_no_line

Prototype: insert_before_if_no_line(before, string)

Description: Insert string before before if string is not found in the file

Arguments:

  • before: The regular expression matching the line which string will be inserted before
  • string: The string to be prepended

Implementation:

code
bundle edit_line insert_before_if_no_line(before, string)
{
  insert_lines:
      "$(string)"
        location => before($(before)),
        comment => "Prepend a line to the file if it doesn't already exist";
}

insert_file

Prototype: insert_file(templatefile)

Description: Reads the lines from templatefile and inserts those into the file being edited.

Arguments:

  • templatefile: The name of the file from which to import lines.

Implementation:

code
bundle edit_line insert_file(templatefile)
{
  insert_lines:

      "$(templatefile)"
      comment => "Insert the template file into the file being edited",
      insert_type => "file";
}

lines_present

Prototype: lines_present(lines)

Description: Ensure lines are present in the file. Lines that do not exist are appended to the file

Arguments:

  • lines: List or string that should be present in the file

Example:

code
bundle agent example
{
 vars:
   "nameservers" slist => { "8.8.8.8", "8.8.4.4" };

 files:
     "/etc/resolv.conf" edit_line => lines_present( @(nameservers) );
     "/etc/ssh/sshd_config" edit_line => lines_present( "PermitRootLogin no" );
}

Implementation:

code
bundle edit_line lines_present(lines)
{
  insert_lines:

      "$(lines)"
        comment => "Append lines if they don't exist";
}

insert_lines

Prototype: insert_lines(lines)

Description: Alias for lines_present

Arguments:

  • lines: List or string that should be present in the file

Implementation:

code
bundle edit_line insert_lines(lines)
{
  insert_lines:

      "$(lines)"
        comment => "Append lines if they don't exist";
}

append_if_no_line

Prototype: append_if_no_line(lines)

Description: Alias for lines_present

Arguments:

  • lines: List or string that should be present in the file

Implementation:

code
bundle edit_line append_if_no_line(lines)
{
  insert_lines:

      "$(lines)"
        comment => "Append lines if they don't exist";
}

append_if_no_lines

Prototype: append_if_no_lines(lines)

Description: Alias for lines_present

Arguments:

  • lines: List or string that should be present in the file

Implementation:

code
bundle edit_line append_if_no_lines(lines)
{
  insert_lines:

      "$(lines)"
        comment => "Append lines if they don't exist";
}

comment_lines_matching

Prototype: comment_lines_matching(regex, comment)

Description: Comment lines in the file that matching an anchored regex

Arguments:

  • regex: Anchored regex that the entire line needs to match
  • comment: A string that is prepended to matching lines

Implementation:

code
bundle edit_line comment_lines_matching(regex,comment)
{
  replace_patterns:

      "^($(regex))$"

      replace_with => comment("$(comment)"),
      comment => "Search and replace string";
}

contains_literal_string

Prototype: contains_literal_string(string)

Description: Ensure the literal string is present in the promised file

Arguments:

  • string: The string (potentially multiline) to ensure exists in the promised file.

Implementation:

code
bundle edit_line contains_literal_string(string)
{

   insert_lines:
     "$(string)"
       insert_type => "preserve_block",
       expand_scalars => "false",
       whitespace_policy => { "exact_match" };
}

uncomment_lines_matching

Prototype: uncomment_lines_matching(regex, comment)

Description: Uncomment lines of the file where the regex matches the entire text after the comment string

Arguments:

  • regex: The regex that lines need to match after comment
  • comment: The prefix of the line that is removed

Implementation:

code
bundle edit_line uncomment_lines_matching(regex,comment)
{
  replace_patterns:

      "^$(comment)\s?($(regex))$"

      replace_with => uncomment,
      comment => "Uncomment lines matching a regular expression";
}

comment_lines_containing

Prototype: comment_lines_containing(regex, comment)

Description: Comment lines of the file matching a regex

Arguments:

  • regex: A regex that a part of the line needs to match
  • comment: A string that is prepended to matching lines

Implementation:

code
bundle edit_line comment_lines_containing(regex,comment)
{
  replace_patterns:

      "^((?!$(comment)).*$(regex).*)$"

      replace_with => comment("$(comment)"),
      comment => "Comment out lines in a file";
}

uncomment_lines_containing

Prototype: uncomment_lines_containing(regex, comment)

Description: Uncomment lines of the file where the regex matches parts of the text after the comment string

Arguments:

  • regex: The regex that lines need to match after comment
  • comment: The prefix of the line that is removed

Implementation:

code
bundle edit_line uncomment_lines_containing(regex,comment)
{
  replace_patterns:

      "^$(comment)\s?(.*$(regex).*)$"

      replace_with => uncomment,
      comment => "Uncomment a line containing a fragment";
}

delete_lines_matching

Prototype: delete_lines_matching(regex)

Description: Delete lines matching a regular expression

Arguments:

  • regex: The regular expression that the lines need to match

Implementation:

code
bundle edit_line delete_lines_matching(regex)
{
  delete_lines:

      "$(regex)"

      comment => "Delete lines matching regular expressions";
}

warn_lines_matching

Prototype: warn_lines_matching(regex)

Description: Warn about lines matching a regular expression

Arguments:

  • regex: The regular expression that the lines need to match

Implementation:

code
bundle edit_line warn_lines_matching(regex)
{
  delete_lines:

      "$(regex)"

      comment => "Warn about lines in a file",
      action => warn_only;
}

prepend_if_no_line

Prototype: prepend_if_no_line(string)

Description: Prepend string if it doesn't exist in the file

Arguments:

  • string: The string to be prepended

See also: insert_lines in [edit_line][bundle edit_line]

Implementation:

code
bundle edit_line prepend_if_no_line(string)
{
  insert_lines:
      "$(string)"
      location => start,
      comment => "Prepend a line to the file if it doesn't already exist";
}

replace_line_end

Prototype: replace_line_end(start, end)

Description: Give lines starting with start the ending given in end

Whitespaces will be left unmodified. For example, replace_line_end("ftp", "2121/tcp") would replace

"ftp 21/tcp"

with

"ftp 2121/tcp"

Arguments:

  • start: The string lines have to start with
  • end: The string lines should end with

Implementation:

code
bundle edit_line replace_line_end(start,end)
{
  field_edits:

      "\s*$(start)\s.*"
      comment => "Replace lines with $(this.start) and $(this.end)",
      edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","set");
}

replace_uncommented_substrings

Prototype: replace_uncommented_substrings(_comment, _find, _replace)

Description: Replace all occurrences of _find with _replace on lines that do not follow a _comment

Arguments:

  • _comment: Sequence of characters, each indicating the start of a comment.
  • _find: String matching substring to replace
  • _replace: String to substitute _find with

Example:

code
bundle agent example_replace_uncommented_substrings
{
  files:
    "/tmp/file.txt"
      edit_line => replace_uncommented_substrings( "#", "ME", "YOU");
}

Notes:

  • Only single character comments are supported as _comment is used in the PCRE character group ([^...]).
  • - in _comment is interpreted as a range unless it's used as the first or last character. For example, setting _comment to 0-9 means any digit starts a comment.

History:

  • Introduced 3.17.0, 3.15.3

Implementation:

code
bundle edit_line replace_uncommented_substrings( _comment, _find, _replace )
{
  vars:
      "_reg_match_uncommented_lines_containing_find"
        string => "^([^$(_comment)]*)\Q$(_find)\E(.*$)";

  replace_patterns:
    "$(_reg_match_uncommented_lines_containing_find)"
      replace_with => text_between_match1_and_match2( $(_replace) );
}

append_to_line_end

Prototype: append_to_line_end(start, end)

Description: Append end to any lines beginning with start

end will be appended to all lines starting with start and not already ending with end. Whitespaces will be left unmodified.

For example, append_to_line_end("kernel", "vga=791") would replace kernel /boot/vmlinuz root=/dev/sda7

with

kernel /boot/vmlinuz root=/dev/sda7 vga=791

WARNING: Be careful not to have multiple promises matching the same line, which would result in the line growing indefinitely.

Arguments:

  • start: pattern to match lines of interest
  • end: string to append to matched lines

Example:

code
 files:
     "/tmp/boot-options" edit_line => append_to_line_end("kernel", "vga=791");

Implementation:

code
bundle edit_line append_to_line_end(start,end)
{
  field_edits:

      "\s*$(start)\s.*"
      comment => "Append lines with $(this.start) and $(this.end)",
      edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","append");
}

regex_replace

Prototype: regex_replace(find, replace)

Description: Find exactly a regular expression and replace exactly the match with a string. You can think of this like a PCRE powered sed.

Arguments:

  • find: The regular expression
  • replace: The replacement string

Implementation:

code
bundle edit_line regex_replace(find,replace)
{
  replace_patterns:

      "$(find)"
      replace_with => value("$(replace)"),
      comment => "Search and replace string";
}

resolvconf

Prototype: resolvconf(search, list)

Description: Adds search domains and name servers to the system resolver configuration.

Use this bundle to modify resolv.conf. Existing entries for search and nameserver are replaced.

Arguments:

  • search: The search domains with space
  • list: An slist of nameserver addresses

Implementation:

code
bundle edit_line resolvconf(search,list)
{
  delete_lines:

      "search.*"     comment => "Reset search lines from resolver";
      "nameserver.*" comment => "Reset nameservers in resolver";

  insert_lines:

      "search $(search)"    comment => "Add search domains to resolver";
      "nameserver $(list)"  comment => "Add name servers to resolver";
}

resolvconf_o

Prototype: resolvconf_o(search, list, options)

Description: Adds search domains, name servers and options to the system resolver configuration.

Use this bundle to modify resolv.conf. Existing entries for search, nameserver and options are replaced.

Arguments:

  • search: The search domains with space
  • list: An slist of nameserver addresses
  • options: is an slist of variables to modify the resolver

Implementation:

code
bundle edit_line resolvconf_o(search,list,options)
{
  delete_lines:

      "search.*"     comment => "Reset search lines from resolver";
      "nameserver.*" comment => "Reset nameservers in resolver";
      "options.*"    comment => "Reset options in resolver";

  insert_lines:

      "search $(search)"    comment => "Add search domains to resolver";
      "nameserver $(list)"  comment => "Add name servers to resolver";
      "options $(options)"  comment => "Add options to resolver";
}

manage_variable_values_ini

Prototype: manage_variable_values_ini(tab, sectionName)

Description: Sets the RHS of configuration items in the file of the form LHS=RHS

If the line is commented out with #, it gets uncommented first. Adds a new line if none exists. Removes any variable value pairs not defined for the ini section.

Arguments:

  • tab: An associative array containing tab[sectionName][LHS]="RHS". The value is not changed when the RHS is "dontchange"
  • sectionName: The section in the file within which values should be modified

See also: set_variable_values_ini()

Implementation:

code
bundle edit_line manage_variable_values_ini(tab, sectionName)
{
  vars:
      "index" slist => getindices("$(tab)[$(sectionName)]");

  delete_lines:
      ".*"
      select_region => INI_section(escape("$(sectionName)")),
      comment       => "Remove all entries in the region so there are no extra entries";

  insert_lines:
      "[$(sectionName)]"
      location => start,
      comment => "Insert lines";

      "$(index)=$($(tab)[$(sectionName)][$(index)])"
      select_region => INI_section(escape("$(sectionName)"));
}

set_variable_values_ini

Prototype: set_variable_values_ini(tab, sectionName)

Description: Sets the RHS of configuration items in the file of the form LHS=RHS

If the line is commented out with #, it gets uncommented first. Adds a new line if none exists.

Arguments:

  • tab: An associative array containing tab[sectionName][LHS]="RHS". The value is not changed when the RHS is "dontchange"
  • sectionName: The section in the file within which values should be modified

See also: manage_variable_values_ini()

Implementation:

code
bundle edit_line set_variable_values_ini(tab, sectionName)
{
  vars:
      "index" slist => getindices("$(tab)[$(sectionName)]");

      # Be careful if the index string contains funny chars
      "cindex[$(index)]" string => canonify("$(index)");

  classes:
      "edit_$(cindex[$(index)])"     not => strcmp("$($(tab)[$(sectionName)][$(index)])","dontchange"),
      comment => "Create conditions to make changes";

  field_edits:

      # If the line is there, but commented out, first uncomment it
      "#+\s*$(index)\s*=.*"
      select_region => INI_section(escape("$(sectionName)")),
      edit_field => col("\s*=\s*","1","$(index)","set"),
      if => "edit_$(cindex[$(index)])";

      # match a line starting like the key something
      "\s*$(index)\s*=.*"
      edit_field => col("\s*=\s*","2","$($(tab)[$(sectionName)][$(index)])","set"),
      select_region => INI_section(escape("$(sectionName)")),
      classes => results("bundle", "set_variable_values_ini_not_$(cindex[$(index)])"),
      if => "edit_$(cindex[$(index)])";

  insert_lines:
      "[$(sectionName)]"
      location => start,
      comment => "Insert lines";

      "$(index)=$($(tab)[$(sectionName)][$(index)])"
      select_region => INI_section(escape("$(sectionName)")),
        if => "!(set_variable_values_ini_not_$(cindex[$(index)])_kept|set_variable_values_ini_not_$(cindex[$(index)])_repaired).edit_$(cindex[$(index)])";

}

insert_ini_section

Prototype: insert_ini_section(name, config)

Description: Inserts a INI section with content

code
# given an array "barray"
files:
    "myfile.ini" edit_line => insert_ini_section("foo", "barray");

Inserts a section in an INI file with the given configuration key-values from the array config.

Arguments:

  • name: the name of the INI section
  • config: The fully-qualified name of an associative array containing v[LHS]="rhs"

Implementation:

code
bundle edit_line insert_ini_section(name, config)
{
  vars:
      # TODO: refactor once 3.7.x is EOL
      "indices" slist => getindices($(config));
      "k" slist => sort("indices", lex);

  insert_lines:
      "[$(name)]"
      location => start,
      comment => "Insert an ini section with values if not present";

      "$(k)=$($(config)[$(k)])"
      location => after("[$(name)]");
}

set_quoted_values

Prototype: set_quoted_values(v)

Description: Sets the RHS of variables in shell-like files of the form:

code
     LHS="RHS"

Adds a new line if no LHS exists, and replaces RHS values if one does exist. If the line is commented out with #, it gets uncommented first.

Arguments:

  • v: The fully-qualified name of an associative array containing v[LHS]="rhs"

Example:

code
    vars:
       "stuff[lhs-1]" string => "rhs1";
       "stuff[lhs-2]" string => "rhs2";

    files:
       "myfile"
         edit_line => set_quoted_values(stuff)

See also: set_variable_values()

Implementation:

code
bundle edit_line set_quoted_values(v)
{
  meta:
      "tags"
      slist =>
      {
        "deprecated=3.6.0",
        "deprecation-reason=Generic reimplementation",
        "replaced-by=set_line_based"
      };

  vars:
      "index" slist => getindices("$(v)");
      # Be careful if the index string contains funny chars

      "cindex[$(index)]" string => canonify("$(index)");

  field_edits:
      # If the line is there, but commented out, first uncomment it
      "#+\s*$(index)\s*=.*"
      edit_field => col("=","1","$(index)","set");

      # match a line starting like the key = something
      "\s*$(index)\s*=.*"
      edit_field => col("=","2",'"$($(v)[$(index)])"',"set"),
      classes    => results("bundle", "$(cindex[$(index)])_in_file"),
      comment    => "Match a line starting like key = something";

  insert_lines:
      '$(index)="$($(v)[$(index)])"'
      comment    => "Insert a variable definition",
        if => "!($(cindex[$(index)])_in_file_kept|$(cindex[$(index)])_in_file_repaired)";
}

set_variable_values

Prototype: set_variable_values(v)

Description: Sets the RHS of variables in files of the form:

code
     LHS=RHS

Adds a new line if no LHS exists, and replaces RHS values if one does exist. If the line is commented out with #, it gets uncommented first.

Arguments:

  • v: The fully-qualified name of an associative array containing v[LHS]="rhs"

Example:

code
    vars:
       "stuff[lhs-1]" string => "rhs1";
       "stuff[lhs-2]" string => "rhs2";

    files:
       "myfile"
         edit_line => set_variable_values(stuff)

See also: set_quoted_values()

Implementation:

code
bundle edit_line set_variable_values(v)
{
  meta:
      "tags"
      slist =>
      {
        "deprecated=3.6.0",
        "deprecation-reason=Generic reimplementation",
        "replaced-by=set_line_based"
      };

  vars:

      "index" slist => getindices("$(v)");

      # Be careful if the index string contains funny chars

      "cindex[$(index)]" string => canonify("$(index)");
      "cv"               string => canonify("$(v)");

  field_edits:

      # match a line starting like the key = something

      "\s*$(index)\s*=.*"

      edit_field => col("\s*$(index)\s*=","2","$($(v)[$(index)])","set"),
      classes => results("bundle", "$(cv)_$(cindex[$(index)])_in_file"),
      comment => "Match a line starting like key = something";

  insert_lines:

      "$(index)=$($(v)[$(index)])"

      comment => "Insert a variable definition",
        if => "!($(cv)_$(cindex[$(index)])_in_file_kept|$(cv)_$(cindex[$(index)])_in_file_repaired)";
}

set_config_values

Prototype: set_config_values(v)

Description: Sets the RHS of configuration items in the file of the form:

code
  LHS RHS

If the line is commented out with #, it gets uncommented first.

Adds a new line if none exists.

Arguments:

  • v: The fully-qualified name of an associative array containing v[LHS]="rhs"

Implementation:

code
bundle edit_line set_config_values(v)
{
  meta:
      "tags"
      slist =>
      {
        "deprecated=3.6.0",
        "deprecation-reason=Generic reimplementation",
        "replaced-by=set_line_based"
      };

  vars:
      "index" slist => getindices("$(v)");

      # Be careful if the index string contains funny chars
      "cindex[$(index)]" string => canonify("$(index)");

      # Escape the value (had a problem with special characters and regex's)
      "ev[$(index)]" string => escape("$($(v)[$(index)])");

      # Do we have more than one line commented out?
      "index_comment_matches_$(cindex[$(index)])"
        int => countlinesmatching("^\s*#\s*($(index)\s+.*|$(index))$","$(edit.filename)");


  classes:
      # Check to see if this line exists
      "line_exists_$(cindex[$(index)])"
        expression => regline("^\s*($(index)\s.*|$(index))$","$(edit.filename)"),
        scope => "bundle";

      # if there's more than one comment, just add new (don't know who to use)
      "multiple_comments_$(cindex[$(index)])"
        expression => isgreaterthan("$(index_comment_matches_$(cindex[$(index)]))","1"),
        scope => "bundle";

  replace_patterns:
      # If the line is commented out, uncomment and replace with
      # the correct value
      "^\s*#\s*($(index)\s+.*|$(index))$"
        comment => "If we find a single commented entry we can uncomment it to
                    keep the settings near any inline documentation. If there
                    are multiple comments, then we don't try to replace them and
                    instead will later append the new value after the first
                    commented occurrence of $(index).",
        handle => "set_config_values_replace_commented_line",
        replace_with => value("$(index) $($(v)[$(index)])"),
                  if => "!line_exists_$(cindex[$(index)]).!replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])",
             classes => results("bundle", "uncommented_$(cindex[$(index)])");

      # If the line is there with the wrong value, replace with
      # the correct value
      "^\s*($(index)\s+(?!$(ev[$(index)])$).*|$(index))$"
           comment => "Correct the value $(index)",
      replace_with => value("$(index) $($(v)[$(index)])"),
           classes => results("bundle", "replace_attempted_$(cindex[$(index)])");

  insert_lines:
      # If the line doesn't exist, or there is more than one occurrence
      # of the LHS commented out, insert a new line and try to place it
      # after the commented LHS (keep new line with old comments)
      "$(index) $($(v)[$(index)])"
         comment => "Insert the value, marker exists $(index)",
        location => after("^\s*#\s*($(index)\s+.*|$(index))$"),
              if => "replace_attempted_$(cindex[$(index)])_reached.multiple_comments_$(cindex[$(index)])";

      # If the line doesn't exist and there are no occurrences
      # of the LHS commented out, insert a new line at the eof
      "$(index) $($(v)[$(index)])"
         comment => "Insert the value, marker doesn't exist $(index)",
              if => "replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])";

}

set_line_based

Prototype: set_line_based(v, sep, bp, kp, cp)

Description: Sets the RHS of configuration items in the file of the form:

code
  LHS$(sep)RHS

Example usage for x=y lines (e.g. rsyncd.conf):

code
"myfile"
edit_line => set_line_based("test.config", "=", "\s*=\s*", ".*", "\s*#\s*");

Example usage for x y lines (e.g. sshd_config):

code
"myfile"
edit_line => set_line_based("test.config", " ", "\s+", ".*", "\s*#\s*");

If the line is commented out with $(cp), it gets uncommented first.

Adds a new line if none exists or if more than one commented-out possible matches exist.

Note: If the data structure being used for the first parameter is in the current bundle, you can use $(this.bundle).variable.

Originally set_config_values by Ed King.

Arguments:

  • v: The fully-qualified name (bundlename.variable) of an associative array containing v[LHS]="rhs"
  • sep: The separator to insert, e.g. for space-separated
  • bp: The key-value separation regex, e.g. \s+ for space-separated
  • kp: The keys to select from v, use .* for all
  • cp: The comment pattern from line-start, e.g. \s*#\s*

Implementation:

code
bundle edit_line set_line_based(v, sep, bp, kp, cp)
{
  meta:
      "tags"
      slist =>
      {
        "replaces=set_config_values",
        "replaces=set_config_values_matching",
        "replaces=set_variable_values",
        "replaces=set_quoted_values",
        "replaces=maintain_key_values",
      };

  vars:
      "vkeys" slist => getindices("$(v)");
      "i" slist => grep($(kp), vkeys);

      # Be careful if the index string contains funny chars
      "ci[$(i)]" string => canonify("$(i)");

      # Escape the value (had a problem with special characters and regex's)
      "ev[$(i)]" string => escape("$($(v)[$(i)])");

      # Do we have more than one line commented out?
      "comment_matches_$(ci[$(i)])"
      int => countlinesmatching("^$(cp)($(i)$(bp).*|$(i))$",
                                $(edit.filename));


  classes:
      # 3.21.0 and greater know about a file being emptied before editing and
      # skip this check since it does not make sense.
@if minimum_version(3.21)
      # Check to see if this line exists
      "exists_$(ci[$(i)])"
      expression => regline("^\s*($(i)$(bp).*|$(i))$",
                            $(edit.filename)),
      unless => strcmp( "true", $(edit.empty_before_use) );
@endif

@if minimum_version(3.18)
    !(cfengine_3_18_0|cfengine_3_18_1|cfengine_3_18_2)::
      "exists_$(ci[$(i)])"
        expression => regline("^\s*($(i)$(bp).*|$(i))$",
                              $(edit.filename)),
        unless => strcmp( "true", $(edit.empty_before_use) );
@endif

    (cfengine_3_15|cfengine_3_16|cfengine_3_17|cfengine_3_18_0|cfengine_3_18_1|cfengine_3_18_2|cfengine_3_19|cfengine_3_20)::
      # Version 3.15.0 does not know about the before_version macro, so we keep the same behavior
      # TODO Remove after 3.21 is no longer supported. (3.15.0 was supported when 3.21 was released)
      # Check to see if this line exists
      "exists_$(ci[$(i)])"
        expression => regline("^\s*($(i)$(bp).*|$(i))$",
                              $(edit.filename));

    any::

      # if there's more than one comment, just add new (don't know who to use)
      "multiple_comments_$(ci[$(i)])"
      expression => isgreaterthan("$(comment_matches_$(ci[$(i)]))",
                                  "1");


  replace_patterns:
      # If the line is commented out, uncomment and replace with
      # the correct value
      "^$(cp)($(i)$(bp).*|$(i))$"
             comment => "Uncommented the value '$(i)'",
        replace_with => value("$(i)$(sep)$($(v)[$(i)])"),
                  if => "!exists_$(ci[$(i)]).!replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)])",
             classes => results("bundle", "uncommented_$(ci[$(i)])");

      # If the line is there with the wrong value, replace with
      # the correct value
      "^\s*($(i)$(bp)(?!$(ev[$(i)])$).*|$(i))$"
           comment => "Correct the value '$(i)'",
      replace_with => value("$(i)$(sep)$($(v)[$(i)])"),
           classes => results("bundle", "replace_attempted_$(ci[$(i)])");

  insert_lines:
      # If the line doesn't exist, or there is more than one occurrence
      # of the LHS commented out, insert a new line and try to place it
      # after the commented LHS (keep new line with old comments)
      "$(i)$(sep)$($(v)[$(i)])"
         comment => "Insert the value, marker '$(i)' exists",
        location => after("^$(cp)($(i)$(bp).*|$(i))$"),
              if => "replace_attempted_$(ci[$(i)])_reached.multiple_comments_$(ci[$(i)])";

      # If the line doesn't exist and there are no occurrences
      # of the LHS commented out, insert a new line at the eof
      "$(i)$(sep)$($(v)[$(i)])"
         comment => "Insert the value, marker '$(i)' doesn't exist",
              if => "replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)]).!exists_$(ci[$(i)])";

  reports:
    verbose_mode|EXTRA::
      "$(this.bundle): Line for '$(i)' exists" if => "exists_$(ci[$(i)])";
      "$(this.bundle): Line for '$(i)' does not exist" if => "!exists_$(ci[$(i)])";
}

set_config_values_matching

Prototype: set_config_values_matching(v, pat)

Description: Sets the RHS of configuration items in the file of the form

code
  LHS RHS

If the line is commented out with #, it gets uncommented first. Adds a new line if none exists.

Arguments:

  • v: the fully-qualified name of an associative array containing v[LHS]="rhs"
  • pat: Only elements of v that match the regex pat are use

Implementation:

code
bundle edit_line set_config_values_matching(v,pat)
{
  meta:
      "tags"
      slist =>
      {
        "deprecated=3.6.0",
        "deprecation-reason=Generic reimplementation",
        "replaced-by=set_line_based"
      };

  vars:
      "allparams" slist => getindices("$(v)");
      "index"     slist => grep("$(pat)", "allparams");

      # Be careful if the index string contains funny chars
      "cindex[$(index)]" string => canonify("$(index)");

  replace_patterns:
      # If the line is there, maybe commented out, uncomment and replace with
      # the correct value
      "^\s*($(index)\s+(?!$($(v)[$(index)])).*|# ?$(index)\s+.*)$"
      comment => "Correct the value",
      replace_with => value("$(index) $($(v)[$(index)])"),
      classes => results("bundle", "replace_attempted_$(cindex[$(index)])");

  insert_lines:
      "$(index) $($(v)[$(index)])"
      if => "replace_attempted_$(cindex[$(index)])_reached";

}

maintain_key_values

Prototype: maintain_key_values(v, sep)

Description: Sets the RHS of configuration items with an giving separator

Contributed by David Lee

Arguments:

  • v of meta promiser tags: string, used to set promise attribute slist of vars promiser index of vars promiser cindex[$(index)] of vars promiser keypat[$(index)]: string, used to set promise attribute string of vars promiser ve[$(index)] of classes promiser $(cindex[$(index)])_key_in_file: string, used to set promise attribute replace_with of replace_patterns promiser $(keypat[$(index]))(?!$(ve[$(index)])$).*, used as promiser of type insert_lines

  • sep of meta promiser tags of vars promiser index of vars promiser cindex[$(index)]: string, used in the value of attribute string of vars promiser keypat[$(index)] of vars promiser ve[$(index)] of classes promiser $(cindex[$(index)])_key_in_file of replace_patterns promiser $(keypat[$(index]))(?!$(ve[$(index)])$).*, used as promiser of type insert_lines

Implementation:

code
bundle edit_line maintain_key_values(v,sep)
{
  meta:
      "tags"
      slist =>
      {
        "deprecated=3.6.0",
        "deprecation-reason=Generic reimplementation",
        "replaced-by=set_line_based"
      };

  vars:
      "index" slist => getindices("$(v)");
      # Be careful if the index string contains funny chars
      "cindex[$(index)]" string => canonify("$(index)");
      # Matching pattern for line (basically key-and-separator)
      "keypat[$(index)]" string => "\s*$(index)\s*$(sep)\s*";

      # Values may contain regexps. Escape them for replace_pattern matching.
      "ve[$(index)]" string => escape("$($(v)[$(index)])");

  classes:
      "$(cindex[$(index)])_key_in_file"
      comment => "Dynamic Class created if patterns matching",
      expression => regline("^$(keypat[$(index)]).*", "$(edit.filename)");

  replace_patterns:
      # For convergence need to use negative lookahead on value:
      # "key sep (?!value).*"
      "^($(keypat[$(index)]))(?!$(ve[$(index)])$).*"
      comment => "Replace definition of $(index)",
      replace_with => value("$(match.1)$($(v)[$(index)])");

  insert_lines:
      "$(index)$(sep)$($(v)[$(index)])"
      comment => "Insert definition of $(index)",
      if => "!$(cindex[$(index)])_key_in_file";
}

append_users_starting

Prototype: append_users_starting(v)

Description: For adding to /etc/passwd or etc/shadow

Arguments:

  • v: An array v[username] string => "line..."

Note: To manage local users with CFEngine 3.6 and later, consider making users promises instead of modifying system files.

Implementation:

code
bundle edit_line append_users_starting(v)
{
  vars:

      "index"        slist => getindices("$(v)");

  classes:

      "add_$(index)"     not => userexists("$(index)"),
      comment => "Class created if user does not exist";

  insert_lines:

      "$($(v)[$(index)])"

      comment => "Append users into a password file format",
      if => "add_$(index)";
}

append_groups_starting

Prototype: append_groups_starting(v)

Description: For adding groups to /etc/group

Arguments:

  • v: An array v[groupname] string => "line..."

Note: To manage local users with CFEngine 3.6 and later, consider making users promises instead of modifying system files.

Implementation:

code
bundle edit_line append_groups_starting(v)
{
  vars:

      "index"        slist => getindices("$(v)");

  classes:

      "add_$(index)"     not => groupexists("$(index)"),
      comment => "Class created if group does not exist";

  insert_lines:

      "$($(v)[$(index)])"

      comment => "Append users into a group file format",
      if => "add_$(index)";

}

set_colon_field

Prototype: set_colon_field(key, field, val)

Description: Set the value of field number field of the line whose first field is key to the value val, in a colon-separated file.

Arguments:

  • key: The value the first field has to match
  • field: The field to be modified
  • val: The new value of field

Implementation:

code
bundle edit_line set_colon_field(key,field,val)
{
  field_edits:

      "$(key):.*"

      comment => "Edit a colon-separated file, using the first field as a key",
      edit_field => col(":","$(field)","$(val)","set");
}

set_user_field

Prototype: set_user_field(user, field, val)

Description: Set the value of field number "field" in a :-field formatted file like /etc/passwd

Arguments:

  • user: The user to be modified
  • field: The field that should be modified
  • val: The value for field

Note: To manage local users with CFEngine 3.6 and later, consider making users promises instead of modifying system files.

Implementation:

code
bundle edit_line set_user_field(user,field,val)
{
  field_edits:

      "$(user):.*"

      comment => "Edit a user attribute in the password file",
      edit_field => col(":","$(field)","$(val)","set");
}

append_user_field

Prototype: append_user_field(group, field, allusers)

Description: For adding users to to a file like /etc/group at field position field, comma separated subfields

Arguments:

  • group: The group to be modified
  • field: The field where users should be added
  • allusers: The list of users to add to field

Note: To manage local users with CFEngine 3.6 and later, consider making users promises instead of modifying system files.

Implementation:

code
bundle edit_line append_user_field(group,field,allusers)
{
  field_edits:

      "$(group):.*"

      comment => "Append users into a password file format",
      edit_field => col(":","$(field)","$(allusers)","alphanum");
}

expand_template

Prototype: expand_template(templatefile)

Description: Read in the named text file and expand $(var) inside the file

Arguments:

  • templatefile: The name of the file

Implementation:

code
bundle edit_line expand_template(templatefile)
{
  insert_lines:

      "$(templatefile)"

      insert_type => "file",
      comment => "Expand variables in the template file",
      expand_scalars => "true";
}

replace_or_add

Prototype: replace_or_add(pattern, line)

Description: Replace a pattern in a file with a single line.

If the pattern is not found, add the line to the file.

Arguments:

  • pattern: The pattern that should be replaced The pattern must match the whole line (it is automatically anchored to the start and end of the line) to avoid ambiguity.
  • line: The line with which to replace matches of pattern

Implementation:

code
bundle edit_line replace_or_add(pattern,line)
{
  vars:
      "cline" string => canonify("$(line)");
      "eline" string => escape("$(line)");

  replace_patterns:
      "^(?!$(eline)$)$(pattern)$"
      comment => "Replace a pattern here",
      replace_with => value("$(line)"),
      classes => results("bundle", "replace_$(cline)");

  insert_lines:
      "$(line)"
      if => "replace_$(cline)_reached";
}

converge

Prototype: converge(marker, lines)

Description: Converge lines marked with marker

Any content marked with marker is removed, then lines are inserted. Every line should contain marker.

Arguments:

  • marker: The marker (not a regular expression; will be escaped)
  • lines: The lines to insert; all must contain marker

Example:

code
bundle agent pam_d_su_include
#@brief Ensure /etc/pam.d/su has includes configured properly
{
  files:
    ubuntu::
      "/etc/pam.d/su"
        edit_line => converge( "@include", "@include common-auth

**Implementation:**


```cf3
bundle edit_line converge(marker, lines)
{
  vars:
      "regex" string => escape($(marker));

  delete_lines:
      ".*$(regex).*" comment => "Delete lines matching the marker";
  insert_lines:
      "$(lines)" comment => "Insert the given lines";
}

converge_prepend

Prototype: converge_prepend(marker, lines)

Description: Converge lines marked with marker to start of content

Any content marked with marker is removed, then lines are inserted at start of content. Every line should contain marker.

Arguments:

  • marker: The marker (not a regular expression; will be escaped)
  • lines: The lines to insert; all must contain marker

Example:

code
bundle agent pam_d_su_session
#@brief Ensure /etc/pam.d/su has session configured properly
{
  files:
    ubuntu::
      "/etc/pam.d/su"
        edit_line => converge_prepend( "session", "session       required   pam_env.so readenv=1 envfile=/etc/default/locale
session    optional   pam_mail.so nopen
session    required   pam_limits.so" );
}

History:

  • Introduced in 3.17.0, 3.15.3, 3.12.6

Implementation:

code
bundle edit_line converge_prepend(marker, lines)
{
  vars:
      "regex" string => escape($(marker));

  delete_lines:
      ".*$(regex).*" comment => "Delete lines matching the marker";
  insert_lines:
      "$(lines)" location => start, comment => "Insert the given lines";
}

fstab_option_editor

Prototype: fstab_option_editor(method, mount, option)

Description: Add or remove /etc/fstab options for a mount

This bundle edits the options field of a mount. The method is a field_operation which can be append, prepend, set, delete, or alphanum. The option is OS-specific.

Arguments:

  • method: field_operation to apply
  • mount: the mount point
  • option: the option to add or remove

Example:

code
 files:
     "/etc/fstab" edit_line => fstab_option_editor("delete", "/", "acl");
     "/etc/fstab" edit_line => fstab_option_editor("append", "/", "acl");

Implementation:

code
bundle edit_line fstab_option_editor(method, mount, option)
{
   field_edits:
      "(?!#)\S+\s+$(mount)\s.+"
      edit_field => fstab_options($(option), $(method));
}

agent bundles

file_mustache

Prototype: file_mustache(mustache_file, json_file, target_file)

Description: Make a file from a Mustache template and a JSON file

Arguments:

  • mustache_file: the file with the Mustache template
  • json_file: a file with JSON data
  • target_file: the target file to write

Example:

code
methods:
     "m" usebundle => file_mustache("x.mustache", "y.json", "z.txt");

Implementation:

code
bundle agent file_mustache(mustache_file, json_file, target_file)
{
  files:
      "$(target_file)"
      create => "true",
      edit_template => $(mustache_file),
      template_data => readjson($(json_file), "100k"),
      template_method => "mustache";
}

file_mustache_jsonstring

Prototype: file_mustache_jsonstring(mustache_file, json_string, target_file)

Description: Make a file from a Mustache template and a JSON string

Arguments:

  • mustache_file: the file with the Mustache template
  • json_string: a string with JSON data
  • target_file: the target file to write

Example:

code
methods:
     "m" usebundle => file_mustache_jsonstring("x.mustache", '{ "x": "y" }', "z.txt");

Implementation:

code
bundle agent file_mustache_jsonstring(mustache_file, json_string, target_file)
{
  files:
      "$(target_file)"
      create => "true",
      edit_template => $(mustache_file),
      template_data => parsejson($(json_string)),
      template_method => "mustache";
}

file_tidy

Prototype: file_tidy(file)

Description: Remove a file

Arguments:

  • file: to remove

Example:

code
methods:
     "" usebundle => file_tidy("/tmp/z.txt");

Implementation:

code
bundle agent file_tidy(file)
{
  files:
      "$(file)" delete => tidy;

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): deleting $(file) with delete => tidy";
}

dir_sync

Prototype: dir_sync(from, to)

Description: Synchronize a directory entire, deleting unknown files

Arguments:

  • from: source directory
  • to: destination directory

Example:

code
methods:
     "" usebundle => dir_sync("/tmp", "/var/tmp");

Implementation:

code
bundle agent dir_sync(from, to)
{
  files:
      "$(to)/."
      create => "true",
      depth_search => recurse("inf"),
      copy_from => copyfrom_sync($(from));

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): copying directory $(from) to $(to)";
}

file_copy

Prototype: file_copy(from, to)

Description: Copy a file

Arguments:

  • from: source file
  • to: destination file

Example:

code
methods:
     "" usebundle => file_copy("/tmp/z.txt", "/var/tmp/y.txt");

Implementation:

code
bundle agent file_copy(from, to)
{
  files:
      "$(to)"
      copy_from => copyfrom_sync($(from));

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): copying file $(from) to $(to)";
}

file_make

Prototype: file_make(file, str)

Description: Make a file from a string

Arguments:

  • file: target
  • str: the string data

Example:

code
methods:
     "" usebundle => file_make("/tmp/z.txt", "Some text
and some more text here");

Implementation:

code
bundle agent file_make(file, str)
{
  vars:
      "len" int => string_length($(str));
    summarize::
      "summary" string => format("%s...%s",
                                string_head($(str), 18),
                                string_tail($(str), 18));
  classes:
      "summarize" expression => isgreaterthan($(len), 40);

  files:
      "$(file)"
      create => "true",
      edit_line => insert_lines($(str)),
      edit_defaults => empty;

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): creating $(file) with contents '$(str)'"
      if => "!summarize";

      "DEBUG $(this.bundle): creating $(file) with contents '$(summary)'"
      if => "summarize";
}

file_make_mog

Prototype: file_make_mog(file, str, mode, owner, group)

Description: Make a file from a string with mode, owner, group

Arguments:

  • file: target
  • str: the string data
  • mode: the file permissions in octal
  • owner: the file owner as a name or UID
  • group: the file group as a name or GID

Example:

code
methods:
     "" usebundle => file_make_mog("/tmp/z.txt", "Some text
and some more text here", "0644", "root", "root");

Implementation:

code
bundle agent file_make_mog(file, str, mode, owner, group)
{
  vars:
      "len" int => string_length($(str));
    summarize::
      "summary" string => format("%s...%s",
                                string_head($(str), 18),
                                string_tail($(str), 18));
  classes:
      "summarize" expression => isgreaterthan($(len), 40);

  files:
      "$(file)"
      create => "true",
      edit_line => insert_lines($(str)),
      perms => mog($(mode), $(owner), $(group)),
      edit_defaults => empty;

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): creating $(file) with contents '$(str)', mode '$(mode)', owner '$(owner)' and group '$(group)'"
      if => "!summarize";

      "DEBUG $(this.bundle): creating $(file) with contents '$(summary)', mode '$(mode)', owner '$(owner)' and group '$(group)'"
      if => "summarize";
}

file_make_mustache

Prototype: file_make_mustache(file, template, data)

Description: Make a file from a mustache template

Arguments:

  • file: Target file to render
  • template: Path to mustache template
  • data: Data container to use

Example:

code
vars:
  "state" data => datastate();

methods:
     "" usebundle => file_make_mustache( "/tmp/z.txt", "/tmp/z.mustache", @(state) );

Implementation:

code
bundle agent file_make_mustache(file, template, data)
{
  files:
      "$(file)"
        create => "true",
        edit_template => "$(template)",
        template_method => "mustache",
        template_data => @(data);

  reports:
      "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): rendering $(file) with template '$(template)'";
}

file_make_mustache_with_perms

Prototype: file_make_mustache_with_perms(file, template, data, mode, owner, group)

Description: Make a file from a mustache template

Arguments:

  • file: Target file to render
  • template: Path to mustache template
  • data: Data container to use
  • mode: File permissions
  • owner: Target file owner
  • group: Target file group

Example:

code
vars:
  "state" data => datastate();

methods:
     "" usebundle => file_make_mustache( "/tmp/z.txt", "/tmp/z.mustache", @(state),
                                         600, "root", "root" );

Implementation:

code
bundle agent file_make_mustache_with_perms(file, template, data, mode, owner, group)
{
  files:
      "$(file)"
        create => "true",
        edit_template => "$(template)",
        template_method => "mustache",
        perms => mog( $(mode), $(owner), $(group) ),
        template_data => @(data);

  reports:
      "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): rendering $(file) with template '$(template)'";
}

file_empty

Prototype: file_empty(file)

Description: Make an empty file

Arguments:

  • file: target

Example:

code
methods:
     "" usebundle => file_empty("/tmp/z.txt");

Implementation:

code
bundle agent file_empty(file)
{
  files:
      "$(file)"
      create => "true",
      edit_defaults => empty;

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): creating empty $(file) with 0 size";
}

Prototype: file_hardlink(target, link)

Description: Make a hard link to a file

Arguments:

  • target: of link
  • link: the hard link's location

Example:

code
methods:
     "" usebundle => file_hardlink("/tmp/z.txt", "/tmp/z.link");

Implementation:

code
bundle agent file_hardlink(target, link)
{
  files:
      "$(link)"
      move_obstructions => "true",
      link_from => linkfrom($(target), "hardlink");

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): $(link) will be a hard link to $(target)";
}

Prototype: file_link(target, link)

Description: Make a symlink to a file

Arguments:

  • target: of symlink
  • link: the symlink's location

Example:

code
methods:
     "" usebundle => file_link("/tmp/z.txt", "/tmp/z.link");

Implementation:

code
bundle agent file_link(target, link)
{
  files:
      "$(link)"
      move_obstructions => "true",
      link_from => linkfrom($(target), "symlink");

  reports:
    "DEBUG|DEBUG_$(this.bundle)"::
      "DEBUG $(this.bundle): $(link) will be a symlink to $(target)";
}