diff --git a/cf-agent/cf-agent.c b/cf-agent/cf-agent.c index 2009183361..313db056d6 100644 --- a/cf-agent/cf-agent.c +++ b/cf-agent/cf-agent.c @@ -1559,6 +1559,15 @@ static void AllClassesReport(const EvalContext *ctx) PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp) // NB - this function can be called recursively through "methods" +{ + if (EvalContextGetEvalOption(ctx, EVAL_OPTION_CLASSIC_EVALUATION)) + { + return ScheduleAgentOperationsNormalOrder(ctx, bp); + } + return ScheduleAgentOperationsTopDownOrder(ctx, bp); +} + +PromiseResult ScheduleAgentOperationsNormalOrder(EvalContext *ctx, const Bundle *bp) { assert(bp != NULL); @@ -1652,6 +1661,54 @@ PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp) return result; } +PromiseResult ScheduleAgentOperationsTopDownOrder(EvalContext *ctx, const Bundle *bp) +{ + assert(bp != NULL); + + int save_pr_kept = PR_KEPT; + int save_pr_repaired = PR_REPAIRED; + int save_pr_notkept = PR_NOTKEPT; + struct timespec start = BeginMeasure(); + + if (PROCESSREFRESH == NULL || (PROCESSREFRESH && IsRegexItemIn(ctx, PROCESSREFRESH, bp->name))) + { + ClearProcessTable(); + } + + PromiseResult result = PROMISE_RESULT_SKIPPED; + for (int pass = 1; pass < CF_DONEPASSES; pass++) + { + const char *last_promise_type = ""; + for (size_t ppi = 0; ppi < SeqLength(bp->all_promises); ppi++) + { + EvalContextSetPass(ctx, pass); + Promise *pp = SeqAt(bp->all_promises, ppi); + BundleSection *parent_section = pp->parent_section; + + if (!StringEqual(last_promise_type, parent_section->promise_type)) + { + SpecialTypeBannerFromString(parent_section->promise_type, pass); + } + last_promise_type = parent_section->promise_type; + + EvalContextStackPushBundleSectionFrame(ctx, parent_section); + + PromiseResult promise_result = ExpandPromise(ctx, pp, KeepAgentPromise, NULL); + result = PromiseResultUpdate(result, promise_result); + if (EvalAborted(ctx) || BundleAbort(ctx)) + { + EvalContextStackPopFrame(ctx); + NoteBundleCompliance(bp, save_pr_kept, save_pr_repaired, save_pr_notkept, start); + return result; + } + EvalContextStackPopFrame(ctx); + } + } + + NoteBundleCompliance(bp, save_pr_kept, save_pr_repaired, save_pr_notkept, start); + return result; +} + /*********************************************************************/ #ifdef __MINGW32__ diff --git a/libpromises/cf3.defs.h b/libpromises/cf3.defs.h index 05d311308b..72555d6a51 100644 --- a/libpromises/cf3.defs.h +++ b/libpromises/cf3.defs.h @@ -441,6 +441,7 @@ typedef enum COMMON_CONTROL_TLS_MIN_VERSION, COMMON_CONTROL_PACKAGE_INVENTORY, COMMON_CONTROL_PACKAGE_MODULE, + COMMON_CONTROL_EVALUATION_ORDER, COMMON_CONTROL_MAX } CommonControl; diff --git a/libpromises/eval_context.h b/libpromises/eval_context.h index a8c935ca40..367dfa5af5 100644 --- a/libpromises/eval_context.h +++ b/libpromises/eval_context.h @@ -109,6 +109,7 @@ typedef enum EVAL_OPTION_EVAL_FUNCTIONS = 1 << 0, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS = 1 << 1, + EVAL_OPTION_CLASSIC_EVALUATION = 1 << 2, EVAL_OPTION_FULL = 0xFFFFFFFF } EvalContextOption; diff --git a/libpromises/expand.c b/libpromises/expand.c index 1a5fdbc1a6..abe27eaa98 100644 --- a/libpromises/expand.c +++ b/libpromises/expand.c @@ -1025,6 +1025,15 @@ static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config, /* Ignored */ } + if (StringEqual(lval, CFG_CONTROLBODY[COMMON_CONTROL_EVALUATION_ORDER].lval)) + { + Log(LOG_LEVEL_VERBOSE, "SET evaluation %s", + RvalScalarValue(evaluated_rval)); + + bool is_classic = (StringEqual(RvalScalarValue(evaluated_rval), "classic")); + EvalContextSetEvalOption(ctx, EVAL_OPTION_CLASSIC_EVALUATION, is_classic); + } + RvalDestroy(evaluated_rval); } diff --git a/libpromises/mod_common.c b/libpromises/mod_common.c index 3e8cbe902b..16b68589b2 100644 --- a/libpromises/mod_common.c +++ b/libpromises/mod_common.c @@ -268,6 +268,7 @@ const ConstraintSyntax CFG_CONTROLBODY[COMMON_CONTROL_MAX + 1] = ConstraintSyntaxNewString("tls_min_version", "", "Minimum acceptable TLS version for outgoing connections, defaults to OpenSSL's default", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewStringList("package_inventory", ".*", "Name of the package manager used for software inventory management", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewString("package_module", ".*", "Name of the default package manager", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewString("evaluation_order", "(classic|top_down)", "Order of evaluation of promises", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewNull() }; diff --git a/libpromises/ornaments.c b/libpromises/ornaments.c index ca37e98d88..469a6626a5 100644 --- a/libpromises/ornaments.c +++ b/libpromises/ornaments.c @@ -130,6 +130,20 @@ void SpecialTypeBanner(TypeSequence type, int pass) } } +void SpecialTypeBannerFromString(const char *type, int pass) +{ + if (StringEqual(type, "classes")) + { + Log(LOG_LEVEL_VERBOSE, "C: ........................................................."); + Log(LOG_LEVEL_VERBOSE, "C: BEGIN classes / conditions (pass %d)", pass); + } + if (StringEqual(type, "vars")) + { + Log(LOG_LEVEL_VERBOSE, "V: ........................................................."); + Log(LOG_LEVEL_VERBOSE, "V: BEGIN variables (pass %d)", pass); + } +} + void PromiseBanner(EvalContext *ctx, const Promise *pp) { char handle[CF_MAXVARSIZE]; diff --git a/libpromises/ornaments.h b/libpromises/ornaments.h index 2688127ce9..921dd1a0cb 100644 --- a/libpromises/ornaments.h +++ b/libpromises/ornaments.h @@ -34,6 +34,7 @@ #include void SpecialTypeBanner(TypeSequence type, int pass); +void SpecialTypeBannerFromString(const char *type, int pass); void PromiseBanner(EvalContext *ctx, const Promise *pp); void Banner(const char *s); void Legend(); diff --git a/libpromises/policy.c b/libpromises/policy.c index 1a79eb2351..4eab215ec0 100644 --- a/libpromises/policy.c +++ b/libpromises/policy.c @@ -1322,6 +1322,7 @@ Bundle *PolicyAppendBundle(Policy *policy, bundle->source_path = SafeStringDuplicate(source_path); bundle->sections = SeqNew(10, BundleSectionDestroy); bundle->custom_sections = SeqNew(10, BundleSectionDestroy); + bundle->all_promises = SeqNew(10, NULL); return bundle; } @@ -1437,6 +1438,7 @@ Promise *BundleSectionAppendPromise(BundleSection *section, const char *promiser { assert(promiser && "Missing promiser"); assert(section != NULL && "Missing promise type"); + assert(section->parent_bundle != NULL); Promise *pp = xcalloc(1, sizeof(Promise)); @@ -1452,6 +1454,7 @@ Promise *BundleSectionAppendPromise(BundleSection *section, const char *promiser } SeqAppend(section->promises, pp); + SeqAppend(section->parent_bundle->all_promises, pp); pp->parent_section = section; @@ -1479,6 +1482,7 @@ static void BundleDestroy(Bundle *bundle) RlistDestroy(bundle->args); SeqDestroy(bundle->sections); SeqDestroy(bundle->custom_sections); + SeqDestroy(bundle->all_promises); free(bundle); } diff --git a/libpromises/policy.h b/libpromises/policy.h index 9f9304faef..cbd75a8849 100644 --- a/libpromises/policy.h +++ b/libpromises/policy.h @@ -79,6 +79,7 @@ struct Bundle_ Seq *sections; Seq *custom_sections; + Seq *all_promises; char *source_path; SourceOffset offset; diff --git a/libpromises/prototypes3.h b/libpromises/prototypes3.h index 1ba16e97d0..8a5334a730 100644 --- a/libpromises/prototypes3.h +++ b/libpromises/prototypes3.h @@ -44,6 +44,8 @@ void yyerror(const char *s); /* agent.c */ PromiseResult ScheduleAgentOperations(EvalContext *ctx, const Bundle *bp); +PromiseResult ScheduleAgentOperationsNormalOrder(EvalContext *ctx, const Bundle *bp); +PromiseResult ScheduleAgentOperationsTopDownOrder(EvalContext *ctx, const Bundle *bp); /* Only for agent.c */ diff --git a/tests/acceptance/15_control/01_common/classic_evaluation_complex_policy.cf b/tests/acceptance/15_control/01_common/classic_evaluation_complex_policy.cf new file mode 100644 index 0000000000..e6b03d63e5 --- /dev/null +++ b/tests/acceptance/15_control/01_common/classic_evaluation_complex_policy.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; + evaluation_order => "classic"; +} +bundle agent test +{ + classes: + "first_class" expression => "any"; + + vars: + first_class:: + "first_var" string => "hello"; + !first_class:: + "first_var" string => "bye"; + + classes: + "second_class" expression => strcmp("$(first_var)", "hello"); + + vars: + second_class:: + "second_var" string => "foo"; + !second_class:: + "second_var" string => "bar"; + + classes: + "third_class" expression => strcmp("$(second_var)", "foo"); + + vars: + third_class:: + "third_var" string => "faz"; + !third_class:: + "thid_var" string => "baz"; + + classes: + "ok" expression => strcmp("$(third_var)", "faz"); + + reports: + !ok:: + "$(this.promise_filename) Pass"; + ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "$(third_var)"; + +} diff --git a/tests/acceptance/15_control/01_common/classic_evaluation_custom_promise_types.cf b/tests/acceptance/15_control/01_common/classic_evaluation_custom_promise_types.cf new file mode 100644 index 0000000000..703d168db2 --- /dev/null +++ b/tests/acceptance/15_control/01_common/classic_evaluation_custom_promise_types.cf @@ -0,0 +1,59 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; + evaluation_order => "classic"; +} + +promise agent dummy +{ + path => "$(this.promise_dirname)/top_down/dummy_promise_type.py"; + interpreter => "/usr/bin/python3"; +} + +bundle agent init +{ + files: + "/tmp/dummyfile" + create => "true"; +} + +bundle agent test +{ + + dummy: + "/tmp/dummyfile" + smth => "hello"; + + vars: + "content" + string => readfile("/tmp/dummyfile"); + + files: + "/tmp/dummyfile" + content => "ok"; + + vars: + "content" + string => readfile("/tmp/dummyfile"); + + classes: + "ok" + expression => strcmp("$(content)", "ok"); + + reports: + !ok:: + "$(this.promise_filename) Pass"; + ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "$(content)"; +} + +bundle agent cleanup +{ + files: + "/tmp/dummyfile" + delete => tidy; +} diff --git a/tests/acceptance/15_control/01_common/classic_evaluation_long_policy.cf b/tests/acceptance/15_control/01_common/classic_evaluation_long_policy.cf new file mode 100644 index 0000000000..ad8404fc1f --- /dev/null +++ b/tests/acceptance/15_control/01_common/classic_evaluation_long_policy.cf @@ -0,0 +1,103 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "test", "cleanup" }; + evaluation_order => "classic"; +} + +bundle agent init +{ + files: + "/tmp/example" + create => "true"; + "/tmp/otherfile" + create => "true"; +} + +bundle agent test +{ + vars: + "result" + string => "First"; + "foo" + string => "second"; + "result" + string => concat("$(result)", ", $(foo)"); + "sixth" + string => "sixth"; + + classes: + "bar" + if => "any"; + + vars: + "foo" + string => "other"; + bar:: + "baz" + string => "third"; + + "result" + string => concat("$(result)", ", $(baz)"); + + files: + "/tmp/example" + content => "fourth"; + + vars: + "content" + string => readfile("/tmp/example"); + "result" + string => concat("$(result)", ", $(content)"); + + classes: + "test" + expression => strcmp("$(content)", "fourth"); + + vars: + test:: + "result" + string => concat("$(result)", ", fifth"); + + + files: + "/tmp/otherfile" + content => "something"; + + vars: + "othercontent" + string => readfile("/tmp/otherfile"); + + files: + "/tmp/otherfile" + delete => tidy; + + classes: + "myclass" + expression => strcmp("$(othercontent)", "something"); + + vars: + myclass:: + "result" + string => concat("$(result)", ", $(sixth)"); + + classes: + "ok" + expression => strcmp("$(result)", "First, second, third, fourth, fifth, sixth"); + + reports: + !ok:: + "$(this.promise_filename) Pass"; + ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "$(result)"; +} + +bundle agent cleanup +{ + files: + "/tmp/example" + delete => tidy; +} diff --git a/tests/acceptance/15_control/01_common/top_down_evaluation_complex_policy.cf b/tests/acceptance/15_control/01_common/top_down_evaluation_complex_policy.cf new file mode 100644 index 0000000000..9377b89464 --- /dev/null +++ b/tests/acceptance/15_control/01_common/top_down_evaluation_complex_policy.cf @@ -0,0 +1,48 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "test" }; + evaluation_order => "top_down"; +} +bundle agent test +{ + classes: + "first_class" expression => "any"; + + vars: + first_class:: + "first_var" string => "hello"; + !first_class:: + "first_var" string => "bye"; + + classes: + "second_class" expression => strcmp("$(first_var)", "hello"); + + vars: + second_class:: + "second_var" string => "foo"; + !second_class:: + "second_var" string => "bar"; + + classes: + "third_class" expression => strcmp("$(second_var)", "foo"); + + vars: + third_class:: + "third_var" string => "faz"; + !third_class:: + "thid_var" string => "baz"; + + classes: + "ok" expression => strcmp("$(third_var)", "faz"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "$(third_var)"; + +} diff --git a/tests/acceptance/15_control/01_common/top_down_evaluation_long_policy.cf b/tests/acceptance/15_control/01_common/top_down_evaluation_long_policy.cf new file mode 100644 index 0000000000..fc11a3eb1d --- /dev/null +++ b/tests/acceptance/15_control/01_common/top_down_evaluation_long_policy.cf @@ -0,0 +1,103 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { "init", "test", "cleanup" }; + evaluation_order => "top_down"; +} + +bundle agent init +{ + files: + "/tmp/example" + create => "true"; + "/tmp/otherfile" + create => "true"; +} + +bundle agent test +{ + vars: + "result" + string => "First"; + "foo" + string => "second"; + "result" + string => concat("$(result)", ", $(foo)"); + "sixth" + string => "sixth"; + + classes: + "bar" + if => "any"; + + vars: + "foo" + string => "other"; + bar:: + "baz" + string => "third"; + + "result" + string => concat("$(result)", ", $(baz)"); + + files: + "/tmp/example" + content => "fourth"; + + vars: + "content" + string => readfile("/tmp/example"); + "result" + string => concat("$(result)", ", $(content)"); + + classes: + "test" + expression => strcmp("$(content)", "fourth"); + + vars: + test:: + "result" + string => concat("$(result)", ", fifth"); + + + files: + "/tmp/otherfile" + content => "something"; + + vars: + "othercontent" + string => readfile("/tmp/otherfile"); + + files: + "/tmp/otherfile" + delete => tidy; + + classes: + "myclass" + expression => strcmp("$(othercontent)", "something"); + + vars: + myclass:: + "result" + string => concat("$(result)", ", $(sixth)"); + + classes: + "ok" + expression => strcmp("$(result)", "First, second, third, fourth, fifth, sixth"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + + DEBUG:: + "$(result)"; +} + +bundle agent cleanup +{ + files: + "/tmp/example" + delete => tidy; +}