cfe_internal/update/update_policy.cf

Table of Contents

copy_from bodies

u_cmdb_data

Prototype: u_cmdb_data

Description: Sync CMDB data from policy server Note: Not all hosts necessarily have CMDB data

Implementation:

body copy_from u_cmdb_data
{
        copy_backup => "false";
        trustkey => "false";
        compare => "digest";
        source  => "hub_cmdb";
        servers => { "$(sys.policy_hub)" };
        purge => "true";
@if minimum_version(3.12)
        missing_ok => "true";
@endif
}

perms bodies

u_m

Prototype: u_m(p)

Description: Ensure file mode is p

Arguments:

  • p: Desired file mode

Implementation:

body perms u_m(p)
{
      mode  => "$(p)";

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

u_mo

Prototype: u_mo(p, o)

Description: Ensure file mode is p and owner is o

Arguments:

  • p: Desired file owner (username or uid)
  • o

Implementation:

#########################################################
{
      mode   => "$(p)";

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

      !(windows|termux)::
        owners => {"$(o)"};
}

u_shared_lib_perms

Prototype: u_shared_lib_perms

Description: Shared library permissions

Implementation:

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

    !hpux::
      mode => "0644";
    hpux::
      mode => "0755"; # Mantis 1114, Redmine 1179
}

file_select bodies

u_all

Prototype: u_all

Description: Select all file system entries

Implementation:

    hpux::
{
        leaf_name => { ".*" };
        file_result => "leaf_name";
}

u_cf3_files

Prototype: u_cf3_files

Implementation:

# @brief Select all file system entries
{
        leaf_name => { ".*" };
        file_result => "leaf_name";
}

u_input_files

Prototype: u_input_files

Description: Select files by extension that we should include when updating inputs

Implementation:

      leaf_name => { "cf-.*" };
{
      leaf_name => { @(update_def.input_name_patterns),
                     @(update_def.input_name_patterns_extra) };
      file_result => "leaf_name";
}

copy_from bodies

u_rcp

Prototype: u_rcp(from, server)

Description: Ensure file is a copy of from on server using digest comparison

Arguments:

  • from: The path to copy from
  • server: The remote host to copy from

Implementation:

                     @(update_def.input_name_patterns_extra) };
{
      source      => "$(from)";
      compare     => "digest";
      trustkey    => "false";
      purge => "true"; # CFE-3662

      # CFE-2932 For testing, we want to be able to avoid this local copy optimiztion
    !am_policy_hub|mpf_skip_local_copy_optimizaton::
      servers => { "$(server)" };

    !am_policy_hub.(sys_policy_hub_port_exists|mpf_skip_local_copy_optimization)::
      portnumber => "$(sys.policy_hub_port)";

    cfengine_internal_encrypt_transfers::
      encrypt => "true";

    cfengine_internal_purge_policies_disabled::
      purge => "false";

    cfengine_internal_preserve_permissions::
      preserve => "true";

    cfengine_internal_verify_update_transfers::
      verify      => "true";
}

u_cp

Prototype: u_cp(from)

Description: Ensure file is a copy from from on the local server based on digest comparison

Arguments:

  • from: string, used in the value of attribute source

Implementation:

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

u_cp_nobck

Prototype: u_cp_nobck(from)

Description: copy from from locally with digest comparison and making no backups

Arguments:

  • from: string, used in the value of attribute source

Implementation:

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

u_cp_missing_ok

Prototype: u_cp_missing_ok(from)

Description: same as u_cp but allow from to be missing

Arguments:

Implementation:

      compare     => "digest";
{
      inherit_from => u_cp($(from));
      missing_ok => "true";
}

action bodies

u_immediate

Prototype: u_immediate

Description: Actuate the promise immediately, ignoring locks

Implementation:

      inherit_from => u_cp($(from));
{
      ifelapsed => "0";
}

depth_search bodies

u_recurse

Prototype: u_recurse(d)

Description: Search recursively for files up to d levels excluding common version control data

Arguments:

  • d: Maximum depth to search recursively

Implementation:

{
{
      depth => "$(d)";
      exclude_dirs => { "\.svn", "\.git", "git-core" };
}

u_infinite_client_policy

Prototype: u_infinite_client_policy

Description: Search recursively for files excluding vcs related files and .no-distrib directories

Implementation:

      depth => "$(d)";
{
        depth => "inf";
        exclude_dirs => { "\.svn", "\.git", "git-core", "\.no-distrib" };
}

u_recurse_basedir

Prototype: u_recurse_basedir(d)

Description: Search recursively for files up to d levels excluding common version control data and including the base directory

Arguments:

  • d: Maximum depth to search recursively

Implementation:

{
{
      include_basedir => "true";
      depth => "$(d)";
      exclude_dirs => { "\.svn", "\.git", "git-core" };
}

classes bodies

u_if_repaired

Prototype: u_if_repaired(x)

Description: Define x if the promise is repaired

Arguments:

  • x: Class to define if promise repaired

Implementation:

      depth => "$(d)";
{
      promise_repaired => { "$(x)" };
}

u_if_repaired_then_cancel

Prototype: u_if_repaired_then_cancel(y)

Description: Cancel class x if the promise is repaired

Arguments:

  • y

Implementation:

{
{
      cancel_repaired => { "$(y)" };
}

u_if_else

Prototype: u_if_else(yes, no)

Description: define yes if the promise is repaired, and no if the promise fails to repair (notkept)

Arguments:

  • yes: Class to define if promise repaired
  • no: Class to undefine if promise notkept

Implementation:

{
{
#      promise_kept     => { "$(yes)" };
      promise_repaired => { "$(yes)" };
      repair_failed    => { "$(no)" };
      repair_denied    => { "$(no)" };
      repair_timeout   => { "$(no)" };
}

contain bodies

u_in_shell

Prototype: u_in_shell

Description: Run command within shell environment

Implementation:

      repair_denied    => { "$(no)" };
{
      useshell => "true";
}

u_in_shell_and_silent

Prototype: u_in_shell_and_silent

Description: Run command within shell environment suppressing output

Implementation:

{
{
      useshell => "true";
      no_output => "true";
}

u_postgres

Prototype: u_postgres

Description: Run command within postgres users shell environment

Implementation:

      useshell => "true";
{
  useshell   => "useshell";
  exec_owner => "cfpostgres";
  exec_group => "cfpostgres";
  chdir      => "/tmp";
  no_output  => "true";
}

action bodies

u_ifwin_bg

Prototype: u_ifwin_bg

Description: Run command in the background if windows is defined

Implementation:

  chdir      => "/tmp";
{
    windows::
      background => "true";
}

service_method bodies

u_bootstart

Prototype: u_bootstart

Description: Attributes for u_bootstart service method

Implementation:

    windows::
{
      service_autostart_policy => "boot_time";
}

contain bodies

u_in_dir

Prototype: u_in_dir(s)

Description: Run command from within s

Arguments:

  • s: Path to change into before running command

Implementation:

{
{
      chdir => "$(s)";
}

u_silent_in_dir

Prototype: u_silent_in_dir(s)

Description: Run command from within s and suppress output

Arguments:

  • s: Path to change into before running command

Implementation:

{
{
      chdir => "$(s)";
      no_output => "true";
}

u_ln_s

Prototype: u_ln_s(x)

Description: Symlink to x, even if it does not exist

Arguments:

  • x: Path to symlink

Implementation:

      chdir => "$(s)";
{
      link_type => "symlink";
      source => "$(x)";
      when_no_source => "force";
}

delete bodies

u_tidy

Prototype: u_tidy

Description: Delete directories and symlinks

Implementation:

      source => "$(x)";
{
      dirlinks => "delete";
      rmdirs   => "true";
}

file_select bodies

not_vendored_modules

Prototype: not_vendored_modules(pathname)

Arguments:

  • pathname

Implementation:

        if => "override_vendored_module_$(_vendored_modules)";
{
  path_name => { "$(pathname)" };
  file_result => "path_name";
}

u_dirs

Prototype: u_dirs

Implementation:

body file_select not_vendored_modules(pathname)
{
  path_name => { "$(pathname)" };
  file_result => "path_name";
}

u_not_dir

Prototype: u_not_dir

Description: Select directories

Implementation:

body file_select u_dirs
{
        file_types  => { "dir" };
        file_result => "file_types";
}

agent bundles

cfe_internal_update_policy

Prototype: cfe_internal_update_policy

Description: This bundle is responsible for activating the policy to update inputs.

Implementation:

bundle agent cfe_internal_update_policy
{
  classes:

      # Define classes if we see a user is requesting a custom policy update bundle

      "have_user_specified_update_bundle"
        expression => isvariable( "def.mpf_update_policy_bundle" );

      # Define classes if we are able to find the specific bundle they requested
      # (otherwise we may get an error about undefined bundle)

      "have_found_user_specified_update_bundle"
        expression  => some(".*", "found_matching_user_specified_bundle");

      "missing_user_specified_update_bundle"
       not  => some(".*", "found_matching_user_specified_bundle");



  vars:
      "default_policy_update_bundle" string => "cfe_internal_update_policy_cpv";

      # Look for a bundle that matches what the user wants
      "found_matching_user_specified_bundle"
        slist => bundlesmatching( "$(def.mpf_update_policy_bundle)" );

  methods:

    # Use the user specified bundle when it's found
    have_found_user_specified_update_bundle::

        "User specified policy update bundle"
          usebundle => $(found_matching_user_specified_bundle);


    # Fall back to stock policy update bundle if we have not found one
    # specified by user

    !have_found_user_specified_update_bundle::

      "Stock policy update"
        usebundle => cfe_internal_update_policy_cpv;

    any::
      "CMDB data update" -> { "ENT-6788", "ENT-8847" }
        usebundle => cfe_internal_update_cmdb,
        action => u_immediate;

  reports:

    inform_mode|verbose_mode|DEBUG|DEBUG_cfe_internal_update_policy::
      # Report a human readable way to understand the policy behavior

      "Found user specified update bundle."
        if => "have_user_specified_update_bundle";

      "User specified update bundle: $(def.mpf_update_policy_bundle)"
        if => "have_user_specified_update_bundle";

      "User specified update bundle MISSING! Falling back to $(default_policy_update_bundle)."
        if => and( "have_user_specified_update_bundle",
                   "missing_user_specified_update_bundle"
                  );


}

Prototype: cfe_internal_setup_python_symlink(symlink_path)

Description: Create the /var/cfengine/bin/python symlink pointing to some installed python (if any)

Arguments:

  • symlink_path of vars promiser path of vars promiser path_folders of vars promiser abs_path_folders of vars promiser abs_path_folders of vars promiser exact_version_globs of vars promiser generic_python_globs of vars promiser python_exact[$(exact_version_globs)] of vars promiser python_generic[$(generic_python_globs)] of vars promiser python_platform_fallback[/usr/libexec/platform-python] of vars promiser python_exact_sorted of vars promiser pythons of vars promiser python, used as promiser of type files , used as promiser of type files of files promiser $(sys.bindir)/python

Implementation:

bundle agent cfe_internal_setup_python_symlink(symlink_path)
{
  vars:
      "path" string => getenv("PATH", 1024);
      "path_folders" slist => splitstring("$(path)", ":", 128);

    windows::
      "abs_path_folders" -> {"CFE-2309"}
        slist => filter("([A-Z]|//):.*", path_folders, "true", "false", 128),
        comment => "findfiles() complains about relative directories";

    !windows::
      "abs_path_folders" -> {"CFE-2309"}
        slist => filter("/.*", path_folders, "true", "false", 128),
        comment => "findfiles() complains about relative directories";

    any::
      "exact_version_globs" slist => maplist("$(this)/python[23]", @(abs_path_folders)),
        comment => "Looking for Python 2 and/or Python 3 in the $PATH folders";

      "generic_python_globs" slist => maplist("$(this)/python", @(abs_path_folders)),
        comment => "Looking for the 'python' symlink/executable which can be any
                    version of Python (usually Python 2 for backwards compatibility)";

      "python_exact[$(exact_version_globs)]" slist => findfiles("$(exact_version_globs)");
      "python_generic[$(generic_python_globs)]" slist => findfiles("$(generic_python_globs)");
      "python_platform_fallback[/usr/libexec/platform-python]" -> { "CFE-3291" }
        slist => { "/usr/libexec/platform-python" };

      "python_exact_sorted" slist => reverse(sort(getvalues(@(python_exact)), "lex")),
        comment => "Prefer higher major versions of Python";

      "pythons" slist => getvalues(mergedata(@(python_exact_sorted),
                                             getvalues(@(python_generic)),
                                             getvalues(@(python_platform_fallback)))),
        comment => "Prefer exact versions over unknown";

      "python" string => nth(@(pythons), 0),
        if => isgreaterthan(length(@(pythons)), 0),
        comment => "Taking the first item from the list (sorted by preference)";

  files:
      "$(symlink_path)"
        delete => u_tidy,
        if => not(isvariable("python"));

      "$(symlink_path)"
        link_from => u_ln_s("$(python)"),
        move_obstructions => "true",
        if => isvariable("python");

      "$(sys.bindir)/python" -> { "CFE-3512" }
        delete => u_tidy,
        if => islink( "$(sys.bindir)/python" ),
        comment => concat( "We don't want to leave a python that is potentially in $PATH ",
                           "after having re-named our python symlink that is used for various ",
                           "modules");

}

cfe_internal_update_policy_cpv

Prototype: cfe_internal_update_policy_cpv

Description: Update inputs from masterfiles when cf_promises_validated changes

Implementation:

bundle agent cfe_internal_update_policy_cpv
{
  vars:
      "inputs_dir"         string => translatepath("$(sys.inputdir)"),
      comment => "Directory containing CFEngine policies",
      handle => "cfe_internal_update_policy_vars_inputs_dir";

      "master_location" -> { "ENT-3692" }
        string => "$(update_def.mpf_update_policy_master_location)",
        comment => "The path to request updates from the policy server.",
        handle => "cfe_internal_update_policy_vars_master_location";

    windows::

      "modules_dir_source"        string => "/var/cfengine/masterfiles/modules",
      comment => "Directory containing CFEngine modules",
      handle => "cfe_internal_update_policy_vars_modules_dir_windows";

    !windows::

      "modules_dir_source"        string => translatepath("$(master_location)/modules"),
      comment => "Directory containing CFEngine modules",
      handle => "cfe_internal_update_policy_vars_modules_dir";

    any::

      "file_check"         string => translatepath("$(inputs_dir)/promises.cf"),
      comment => "Path to a policy file",
      handle => "cfe_internal_update_vars_file_check";

      "ppkeys_file"        string => translatepath("$(sys.workdir)/ppkeys/localhost.pub"),
      comment => "Path to public key file",
      handle => "cfe_internal_update_policy_vars_ppkeys_file";

      "postgresdb_dir"        string => "$(sys.workdir)/state/pg/data",
      comment => "Directory where Postgres database files will be stored on hub -",
      handle => "cfe_internal_update_policy_postgresdb_dir";

      "postgresdb_log"        string => "/var/log/postgresql.log",
      comment => "File where Postgres database files will be logging -",
        handle => "cfe_internal_update_policy_postgresdb_log_file";

      "python_symlink" -> { "CFE-2602", "CFE-3512" }
        string => "$(sys.bindir)/cfengine-selected-python",
        comment => "Symlink to Python we found (if any)",
        handle => "cfe_internal_update_policy_python_symlink";

    cfredis_in_enterprise::

      # TODO Remove from MPF after 3.12 EOL

      "redis_conf_file" -> { "ENT-2797" }
        string => translatepath("$(sys.workdir)/config/redis.conf"),
        comment => "Path to Redis configuration file",
        handle => "cfe_internal_update_policy_redis_conf_file";

  classes:

      "validated_updates_ready"
        expression => "cfengine_internal_disable_cf_promises_validated",
        comment => "If cf_promises_validated is disabled, then updates are
                    always considered validated.";

    any::

      "local_files_ok" expression => fileexists("$(file_check)"),
      comment => "Check for $(sys.masterdir)/promises.cf",
      handle => "cfe_internal_update_classes_files_ok";

      # create a global files_ok class
      "cfe_internal_trigger" expression => "local_files_ok",
      classes => u_if_else("files_ok", "files_ok");

  files:

    !am_policy_hub::  # policy hub should not alter inputs/ uneccessary

      "$(inputs_dir)/cf_promises_validated"
      comment => "Check whether a validation stamp is available for a new policy update to reduce the distributed load",
      handle => "cfe_internal_update_policy_check_valid_update",
      copy_from => u_rcp("$(master_location)/cf_promises_validated", @(update_def.policy_servers)),
      action => u_immediate,
      classes => u_if_repaired("validated_updates_ready");

    am_policy_hub|validated_updates_ready::  # policy hub should always put masterfiles in inputs in order to check new policy

      "$(inputs_dir)"
      comment => "Copy policy updates from master source on policy server if a new validation was acquired",
      handle => "cfe_internal_update_policy_files_inputs_dir",
      copy_from => u_rcp("$(master_location)", @(update_def.policy_servers)),
      depth_search => u_infinite_client_policy,
      file_select  => u_input_files,
      action => u_immediate,
      classes => u_results("bundle", "update_inputs"),
      move_obstructions => "true";

      # Note that here we do not filter with `update_def.input_name_patterns` so
      # that we copy any and all modules scripts.
      "$(inputs_dir)/modules"
      comment => "Copy any files in modules from master source on policy server if a new validation was acquired",
      handle => "cfe_internal_update_policy_files_modules_dir",
      copy_from => u_rcp("$(modules_dir_source)", @(update_def.policy_servers)),
      depth_search => u_recurse("inf"),
      action => u_immediate;

    update_inputs_not_kept::

      "$(inputs_dir)/cf_promises_validated" -> { "CFE-2587" }
        delete => u_tidy,
        comment => "If there is any problem copying to $(inputs_dir) then purge
                    the cf_promises_validated file must be purged so that
                    subsequent agent runs will perform a full scan.";

    !policy_server.enable_cfengine_enterprise_hub_ha::
      "$(sys.workdir)/policy_server.dat"
      comment => "Copy policy_server.dat file from server",
      handle => "cfe_internal_update_ha_policy_server",
      copy_from => u_rcp("$(sys.workdir)/state/master_hub.dat", @(update_def.policy_servers)),
      action => u_immediate,
      classes => u_if_repaired("replica_failover");  # not needed ?

    am_policy_hub::

      "$(master_location)/." -> { "CFE-951" }
        comment => "Make sure masterfiles folder has right file permissions",
        handle => "cfe_internal_update_policy_files_sys_workdir_masterfiles_dirs",
        perms => u_m($(update_def.masterfiles_perms_mode_dirs)),
        file_select => u_dirs,
        depth_search => u_recurse_basedir("inf"),
        action => u_immediate;

      "$(master_location)/." -> { "CFE-951" }
        comment => "Make sure masterfiles folder has right file permissions",
        handle => "cfe_internal_update_policy_files_sys_workdir_masterfiles_not_dir",
        perms => u_m($(update_def.masterfiles_perms_mode_not_dir)),
        file_select => u_not_dir,
        depth_search => u_recurse_basedir("inf"),
        action => u_immediate;


  methods:
    debian|redhat|amazon_linux|suse|sles|opensuse::
      # Only needed on distros with Python-based package modules
      "setup_python_symlink" -> { "CFE-2602" }
        usebundle => cfe_internal_setup_python_symlink("$(python_symlink)");

    any::
      # Install vendored and user provided modules to $(sys.workdir) from $(sys.inputdir)
      "modules_presence";
}

cfe_internal_update_cmdb

Prototype: cfe_internal_update_cmdb

Description: Ensure local cache of CMDB data is up to date

Implementation:

bundle agent cfe_internal_update_cmdb
{
  classes:
      "have_cf_reactor" expression => fileexists("$(sys.bindir)/cf-reactor");

  methods:

    policy_server.enterprise_edition.(!have_cf_reactor|cmdb_data_files_updates_done_in_policy)::
      "cfe_internal_update_cmdb_data_distribution";

@if feature(host_specific_data_load)
      # Only hosts with this feature, introduced in 3.18.0 can use the data.

      # Don't pull CMDB data on policy_hub self bootstrap because
      # there will be no cf-serverd listening to serve files yet.
    enterprise_edition.!(bootstrap_mode)::  # ENT-6840
      "cfe_internal_update_cmdb_data_consumption" -> { "ENT-8847" }
        action => u_immediate;
@endif
}

cfe_internal_update_cmdb_data_distribution

Prototype: cfe_internal_update_cmdb_data_distribution

Description: Ensure data is ready for agents to download

Implementation:

bundle agent cfe_internal_update_cmdb_data_distribution
{
  classes:

      "_have_cmdb_next_request_state_file" -> { "ENT-9933" }
        expression => fileexists( "$(_cmdb_next_request_state_file)" );

  vars:
    !bootstrap_mode.(policy_server.enterprise_edition)::

      # The API response for host specific data from cmdb tells us the timestamp of the last data change
      # We store this timestamp and use it for the next request.
      "_cmdb_next_request_state_file"
        string => "$(sys.statedir)/cmdb_next_request_from.dat";

    !bootstrap_mode.(policy_server.enterprise_edition._have_cmdb_next_request_state_file)::
      # If we have the timestamp from a previous response we use it, else we start from 0
      "_cmdb_previous_next_request_from"
        string => readfile( $(_cmdb_next_request_state_file), inf ),
        if => regline( "^\d+$", $(_cmdb_next_request_state_file) );

      "_cmdb_previous_next_request_from"
        string => "0",
        unless => regline( "^\d+$", $(_cmdb_next_request_state_file) );

    !bootstrap_mode.(policy_server.enterprise_edition)::
      # We need a script to call that should return the API response
      "_get_cmdb_data_bin" string => "$(sys.workdir)/httpd/htdocs/scripts/get_cmdb.php";
      "_get_cmdb_data_cmd" string => "/var/cfengine/httpd/php/bin/php $(_get_cmdb_data_bin) $(_cmdb_previous_next_request_from)";

      # We call the script and we pass it the timestamp from the prior call
      "_get_cmdb_data_response"
        string => execresult( $(_get_cmdb_data_cmd), useshell ),
        if => fileexists( $(_get_cmdb_data_bin) );

      "_get_cmdb_data_response_d"
        data => parsejson('$(_get_cmdb_data_response)'),
        if => validjson( '$(_get_cmdb_data_response)' );

      # So that we can write a JSON file for each host we get the indicies of data in the response
      "_i" slist => getindices( "_get_cmdb_data_response_d[data]");

      # We need to store the timestamp from the most recent change so that we can use that as a starting point for future requests.
      "_next_request_from"
        string => "$(_get_cmdb_data_response_d[meta][cmdb_epoch])";

  files:
      # "$(_get_cmdb_data_cmd)" perms => m( 700 );

@if minimum_version(3.18)
    !bootstrap_mode.(policy_server.enterprise_edition)::
      # This functionality is only present on 3.18.0+ Enterprise hubs, and this
      # promise uses the /content/ attribute which was first introduced in
      # 3.16.0.

      # If the next request state file doesn't exist, we seed one with 0, the
      # lowest epoch value possible. because we populate variables from this
      # file content.
      "$(_cmdb_next_request_state_file)"
        content => "0$(const.n)",
        handle => "cmdb_data_change_next_seed",
        if => and( not(fileexists("$(_cmdb_next_request_state_file)" )),
                   isvariable( "_cmdb_next_request_state_file" ));
@endif

      # Write out the data for each host that had a data change
      "$(sys.workdir)/cmdb/$(_i)/host_specific.json"
        create => "true", # CFE-2329, ENT-4792
        template_data => mergedata("_get_cmdb_data_response_d[data][$(_i)]" ), # mergedata() is necessary in order to pick out a substructure, parsejson() is insufficient because expanding a key results in iteration of /values/ under that key
        template_method => "inline_mustache",
        edit_template_string => string_mustache( "", "_get_cmdb_data_response_d[data][$(_i)]" ),
        if => isgreaterthan( $(_next_request_from), $(_cmdb_previous_next_request_from) );

@if minimum_version(3.18)
      # This functionality is only present on 3.18.0+ Enterprise hubs, and this
      # promise uses the /content/ attribute which was first introduced in
      # 3.16.0.

      # Write out the last data change timestamp so we can use it as a startring point
      "$(_cmdb_next_request_state_file)"
        handle => "cmdb_data_change_next_update",
        content => "$(_next_request_from)$(const.n)",
        unless => strcmp( $(_next_request_from), $(_cmdb_previous_next_request_from) );
@endif

  reports:
    DEBUG|DEBUG_cfe_internal_update_cmdb_data_distribution::
      "'$(_get_cmdb_data_cmd)' response indicates '$(sys.workdir)/cmdb/$(_i)/host_specific.json' needs refreshed"
        if => and( isvariable( "_i" ),
                   isgreaterthan( $(_next_request_from), $(_cmdb_previous_next_request_from) ));

}

cfe_internal_update_cmdb_data_consumption

Prototype: cfe_internal_update_cmdb_data_consumption

Description: Ensure data to load is up to date

Implementation:

bundle agent cfe_internal_update_cmdb_data_consumption
{
  files:
      "$(sys.workdir)/data/."
        create => "true",
        comment => "If a host is to load data from the CMDB, it needs to have a directory where said data is cached.";

      "$(sys.workdir)/data/." -> { "ENT-6788", "ENT-8847" }
        depth_search => u_recurse( inf ),
        file_select => u_all,
        copy_from => u_cmdb_data,
        comment => "So that hosts have access to the most recent CMDB data, we make sure that it's up to date.",
        action => u_immediate;

}

modules_presence

Prototype: modules_presence

Implementation:

#@ ```
{
      dirlinks => "delete";
      rmdirs   => "true";
}