# Common bodies

##-------------------------------------------------------
## action
##-------------------------------------------------------

body action if_elapsed(x)
# @brief Evaluate the promise every `x` minutes
# @param x The time in minutes between promise evaluations
{
      ifelapsed => "$(x)";
      expireafter => "$(x)";
}

##

body action if_elapsed_day
# @brief Evaluate the promise once every 24 hours
{
      ifelapsed => "1440";    # 60 x 24
      expireafter => "1400";
}

##

body action measure_performance(x)
# @brief Measure repairs of the promiser every `x` minutes
#
# Repair-attempts are cancelled after `x` minutes.
#
# @param x The time in minutes between promise evaluations.
{
      measurement_class => "Detect changes in $(this.promiser)";
      ifelapsed => "$(x)";
      expireafter => "$(x)";
}

##

body action measure_promise_time(identifier)
# @brief Performance will be measured and recorded under identifier
#
# @param identifier Measurement name.
{
      measurement_class => "$(identifier)";
}

##

body action warn_only
# @brief Warn once an hour if the promise needs to be repaired
#
# The promise does not get repaired.
{
      action_policy => "warn";
      ifelapsed => "60";
}

##

body action bg(elapsed, expire)
# @brief Evaluate the promise in the background every `elapsed` minutes, for at most `expire` minutes
# @param elapsed The time in minutes between promise evaluations
# @param expire The time in minutes after which a repair-attempt gets cancelled
{
      ifelapsed   => "$(elapsed)";
      expireafter => "$(expire)";
      background  => "true";
}

##

body action ifwin_bg
# @brief Evaluate the promise in the background when running on Windows
{
    windows::
      background => "true";
}

##

body action immediate
# @brief Evaluate the promise at every `cf-agent` execution.
{
      ifelapsed => "0";
}

##

body action policy(p)
# @brief Set the `action_policy` to `p`
# @param p The action policy
{
      action_policy => "$(p)";
}

##

body action log_repaired(log, message)
# @brief Log `message` to a file `log`=[/file|stdout]
# @param log The log file for repaired messages
# @param message The log message
{
      log_string => "$(sys.date), $(message)";
      log_repaired => "$(log)";
}

###

body action log_verbose
# @brief Sets the `log_level` attribute to "verbose"
{
      log_level => "verbose";
}

##

body action sample_rate(x)
# @brief Evaluate the promise every `x` minutes
#
# A repair-attempt is cancelled after 10 minutes.
#
# @param x The time in minutes between promise evaluation
{
      ifelapsed => "$(x)";
      expireafter => "10";
}

##-------------------------------------------------------
## classes
##-------------------------------------------------------

body classes if_repaired(x)
# @brief Define class `x` if the promise has been repaired
# @param x The name of the class
{
      promise_repaired => { "$(x)" };
}

##

body classes if_else(yes, no)
# @brief Define the classes `yes` or `no` depending on promise outcome
# @param yes The name of the class that should be defined if the promise is kept or repaired
# @param no The name of the class that should be defined if the promise could not be repaired
{
      promise_kept     => { "$(yes)" };
      promise_repaired => { "$(yes)" };
      repair_failed    => { "$(no)" };
      repair_denied    => { "$(no)" };
      repair_timeout   => { "$(no)" };
}

##

body classes cf2_if_else(yes, no)
# @brief Define the classes `yes` or `no`, depending on promise outcome
#
# A version of `if_else` that matches CFEngine2 semantics. Neither class is set if the promise
# does not require any repair.
#
# @param yes The name of the class that should be defined if the promise is repaired
# @param no The name of the class that should be defined if the promise could not be repaired
{
      promise_repaired => { "$(yes)" };
      repair_failed    => { "$(no)" };
      repair_denied    => { "$(no)" };
      repair_timeout   => { "$(no)" };
}

##

body classes if_notkept(x)
# @brief Define the class `x` if the promise is not kept and cannot be repaired.
# @param x The name of the class that should be defined
{
      repair_failed   => { "$(x)" };
      repair_denied   => { "$(x)" };
      repair_timeout  => { "$(x)" };
}

##

body classes if_ok(x)
# @brief Define the class `x` if the promise is kept or repaired
# @param x The name of the class that should be defined
{
      promise_repaired => { "$(x)" };
      promise_kept => { "$(x)" };
}

##

body classes if_ok_cancel(x)
# @brief Cancel the class `x` if the promise is kept or repaired
# @param x The name of the class that should be cancelled
{
      cancel_repaired => { "$(x)" };
      cancel_kept => { "$(x)" };
}

##

body classes cmd_repair(code, cl)
# @brief Define the class `cl` if an external command in a `commands`, `file` or `packages`
# promise is executed with return code `code`
# @param code The return codes that indicate a successful repair
# @param cl The name of the class that should be defined
#
# **See also:** `repaired_returncodes`
{
      repaired_returncodes => { "$(code)" };
      promise_repaired => { "$(cl)" };
}

body classes classes_generic(x)
# @brief Define `x` prefixed/suffixed with promise outcome
# @param x The unique part of the classes to be defined
{
      promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" };
      repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_reached" };
}

body classes results(scope, class_prefix)
# @brief Define classes prefixed with `class_prefix` and suffixed with
# appropriate outcomes: `_kept`, `_repaired`, `_not_kept`, `_error`, `_failed`,
# `_denied`, `_timeout`, `_reached`
#
# @param scope The scope in which the class should be defined (`bundle` or `namespace`)
# @param class_prefix The prefix for the classes defined
#
# This body can be applied to any promise and sets global
# (`namespace`) or local (`bundle`) classes based on its outcome. For
# instance, with `class_prefix` set to `abc`:
#
# * if the promise is to change a file's owner to `nick` and the file
# was already owned by `nick`, the classes `abc_reached` and
# `abc_kept` will be set.
#
# * if the promise is to change a file's owner to `nick` and the file
# was owned by `adam` and the change succeeded, the classes
# `abc_reached` and `abc_repaired` will be set.
#
# This body is a simpler, more consistent version of the body
# `scoped_classes_generic`, which see. The key difference is that
# fewer classes are defined, and only for outcomes that we can know.
# For example this body does not define "OK"/"not OK" outcome classes,
# since a promise can be both kept and failed at the same time.
#
# It's important to understand that promises may do multiple things,
# so a promise is not simply "OK" or "not OK." The best way to
# understand what will happen when your specific promises get this
# body is to test it in all the possible combinations.
#
# **Suffix Notes:**
#
# * `_reached` indicates the promise was tried. Any outcome will result
#   in a class with this suffix being defined.
#
# * `_kept` indicates some aspect of the promise was kept
#
# * `_repaired` indicates some aspect of the promise was repaired
#
# * `_not_kept` indicates some aspect of the promise was not kept.
#   error, failed, denied and timeout outcomes will result in a class
#   with this suffix being defined
#
# * `_error` indicates the promise repair encountered an error
#
# * `_failed` indicates the promise failed
#
# * `_denied` indicates the promise repair was denied
#
# * `_timeout` indicates the promise timed out
#
# **Example:**
#
# ```cf3
# bundle agent example
# {
#   commands:
#     "/bin/true"
#       classes => results("bundle", "my_class_prefix");
#
#   reports:
#     my_class_prefix_kept::
#       "My promise was kept";
#
#     my_class_prefix_repaired::
#       "My promise was repaired";
# }
# ```
#
# **See also:** `scope`, `scoped_classes_generic`, `classes_generic`
{
  scope => "$(scope)";

  promise_kept => { "$(class_prefix)_reached",
                    "$(class_prefix)_kept" };

  promise_repaired => { "$(class_prefix)_reached",
                        "$(class_prefix)_repaired" };

  repair_failed => { "$(class_prefix)_reached",
                     "$(class_prefix)_error",
                     "$(class_prefix)_not_kept",
                     "$(class_prefix)_failed" };

  repair_denied => { "$(class_prefix)_reached",
                     "$(class_prefix)_error",
                     "$(class_prefix)_not_kept",
                     "$(class_prefix)_denied" };

  repair_timeout => { "$(class_prefix)_reached",
                      "$(class_prefix)_error",
                      "$(class_prefix)_not_kept",
                      "$(class_prefix)_timeout" };
}

@if minimum_version(3.8)
body classes diff_results(scope, x)
# @brief Define `x` prefixed/suffixed with promise outcome with command return codes adjusted to align with `diff`.
# @param scope The scope the class should be defined with (`bundle` or `namespace`).
# @param x The unique part of the classes to be defined.
#
# From man diff:
# Exit status is 0 if inputs are the  same,  1  if
# different, 2 if trouble.
#
# **Example:**
#
# ```cf3
# bundle agent example
# {
#   commands:
#     "/usr/bin/diff"
#       args => "/tmp/file1 /tmp/file2",
#       classes => diff_results("diff");
#
#   vars:
#     "c" slist => classesmatching("diff_.*");
#
#   reports:
#     "Found class '$(c)'";
#     "Files Differ!"
#        if => "diff_failed|diff_error|diff_not_kept";
#     "Files are the same."
#        if => "diff_kept";
# }
# ```
{
      inherit_from => results( $(scope), $(x) );
      kept_returncodes => { "0" };
      failed_returncodes => { "1", "2" };
}
@endif

body classes scoped_classes_generic(scope, x)
# @brief Define `x` prefixed/suffixed with promise outcome
# **See also:** `scope`
#
# @param scope The scope in which the class should be defined
# @param x The unique part of the classes to be defined
{
      scope => "$(scope)";
      promise_repaired => { "promise_repaired_$(x)", "$(x)_repaired", "$(x)_ok", "$(x)_reached" };
      repair_failed => { "repair_failed_$(x)", "$(x)_failed", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      repair_denied => { "repair_denied_$(x)", "$(x)_denied", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      repair_timeout => { "repair_timeout_$(x)", "$(x)_timeout", "$(x)_not_ok", "$(x)_error", "$(x)_not_kept", "$(x)_reached" };
      promise_kept => { "promise_kept_$(x)", "$(x)_kept", "$(x)_ok", "$(x)_reached" };
}

##-------------------------------------------------------
## Persistent classes
##-------------------------------------------------------

body classes state_repaired(x)
# @brief Define `x` for 10 minutes if the promise was repaired
# @param x The name of the class that should be defined
{
      promise_repaired => { "$(x)" };
      persist_time => "10";
      scope => "namespace";
}

##

body classes enumerate(x)
# @brief Define `x` for 15 minutes if the promise is either kept or repaired
# This is used by commercial editions to count instances of jobs in a cluster
# @param x The unique part of the class that should be defined
# The class defined is prefixed with `mXC_`
{
      promise_repaired => { "mXC_$(x)" };
      promise_kept => { "mXC_$(x)" };
      persist_time => "15";
      scope => "namespace";
}

##

body classes always(x)
# @brief Define class `x` no matter what the outcome of the promise is
# @param x The name of the class to be defined
{
      promise_repaired => { "$(x)" };
      promise_kept => { "$(x)" };
      repair_failed => { "$(x)" };
      repair_denied => { "$(x)" };
      repair_timeout => { "$(x)" };
}

body classes kept_successful_command
# @brief Set command to "kept" instead of "repaired" if it returns 0
{
      kept_returncodes => { "0" };
}
