diff --git a/examples/classfilterdata_array_of_arrays.cf b/examples/classfilterdata_array_of_arrays.cf new file mode 100644 index 0000000000..5c0786c313 --- /dev/null +++ b/examples/classfilterdata_array_of_arrays.cf @@ -0,0 +1,39 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '[ + [ "role_1", "alice", 32 ], + [ "!role_1", "bob", 24 ], + [ "role_2", "malcom", 27 ] + ]'; + + "filtered" + data => classfilterdata("original", "array_of_arrays", "0"); + + reports: + "Filtered data: $(with)" + with => storejson("filtered"); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Filtered data: [ +#@ [ +#@ "!role_1", +#@ "bob", +#@ 24 +#@ ], +#@ [ +#@ "role_2", +#@ "malcom", +#@ 27 +#@ ] +#@ ] +#@ ``` +#+end_src diff --git a/examples/classfilterdata_array_of_objects.cf b/examples/classfilterdata_array_of_objects.cf new file mode 100644 index 0000000000..224cde4924 --- /dev/null +++ b/examples/classfilterdata_array_of_objects.cf @@ -0,0 +1,37 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '[ + { "file": "/tmp/foo", "ifvarclass": "role_1" }, + { "file": "/tmp/bar", "ifvarclass": "role_2" }, + { "file": "/tmp/baz", "ifvarclass": "(role_1|role_2)" } + ]'; + + "filtered" + data => classfilterdata("original", "array_of_objects", "ifvarclass"); + + reports: + "Filtered data: $(with)" + with => storejson("filtered"); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Filtered data: [ +#@ { +#@ "file": "/tmp/bar", +#@ "ifvarclass": "role_2" +#@ }, +#@ { +#@ "file": "/tmp/baz", +#@ "ifvarclass": "(role_1|role_2)" +#@ } +#@ ] +#@ ``` +#+end_src diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 44774e8a4c..6c44694363 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -22,6 +22,7 @@ included file COSL.txt. */ +#include #include #include @@ -35,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -988,7 +991,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli assert(fp != NULL); bool allocated = false; JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); - + // we failed to produce a valid JsonElement, so give up if (json == NULL) { @@ -1003,7 +1006,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli JsonDestroyMaybe(json, allocated); return FnFailure(); } - + JsonElement *parent = JsonObjectCreate(10); setpwent(); struct passwd *pw; @@ -1048,7 +1051,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli { char uid_string[PRINTSIZE(pw->pw_uid)]; int ret = snprintf(uid_string, sizeof(uid_string), "%u", pw->pw_uid); - + if (ret < 0) { Log(LOG_LEVEL_ERR, "Couldn't convert the uid of '%s' to string in function '%s'", @@ -1080,7 +1083,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli assert((size_t) ret < sizeof(gid_string)); if (!StringMatchFull(value, gid_string)) - { + { can_add_to_json = false; } } @@ -1105,13 +1108,13 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli can_add_to_json = false; } } - else + else { Log(LOG_LEVEL_ERR, "Invalid attribute '%s' in function '%s': not supported", attribute, fp->name); JsonDestroyMaybe(json, allocated); JsonDestroy(parent); - return FnFailure(); + return FnFailure(); } element = JsonIteratorNextValue(&iter); } @@ -1409,7 +1412,7 @@ static FnCallResult FnCallGetGid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const P #endif } -/*********************************************************************/ +/*********************************************************************/ static FnCallResult no_entry(int ret, const FnCall *fp, const char *group_name, bool is_user_db) { @@ -1433,7 +1436,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co assert(finalargs != NULL); #ifdef _WIN32 Log(LOG_LEVEL_ERR, "Function '%s' is POSIX specific", fp->name); - return FnFailure(); + return FnFailure(); #else const char *user_name = RlistScalarValue(finalargs); @@ -1447,7 +1450,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co struct group *grent; char gr_buf[GETGR_R_SIZE_MAX] = {0}; ret = getgrnam_r(group_name, &grp, gr_buf, GETGR_R_SIZE_MAX, &grent); - + if (grent == NULL) { // Group does not exist at all, so cannot be @@ -1474,7 +1477,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co return no_entry(ret, fp, user_name, true); } return FnReturnContext(grent->gr_gid == pwent->pw_gid); - + #endif } @@ -7777,6 +7780,186 @@ static int JsonPrimitiveComparator(JsonElement const *left_obj, return StringSafeCompare(left, right); } +static bool ClassFilterDataArrayOfArrays( + EvalContext *ctx, + const char *fn_name, + JsonElement *json_array, + const char *class_expr_index, + bool *remove) +{ + size_t index; + assert(SIZE_MAX >= ULONG_MAX); /* make sure returned value can fit in size_t */ + if (StringToUlong(class_expr_index, &index) != 0) { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression index '%s': Failed to parse integer", + fn_name, class_expr_index); + return false; + } + + size_t length = JsonLength(json_array); + if (index >= length) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression index '%s': Index out of bounds (%zu >= %zu)", + fn_name, class_expr_index, index, length); + return false; + } + + JsonElement *json_child = JsonArrayGet(json_array, index); + if (JsonGetType(json_child) != JSON_TYPE_STRING) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression at index '%zu': Expected type string", + fn_name, index); + return false; + } + + const char *class_expr = JsonPrimitiveGetAsString(json_child); + assert(class_expr != NULL); + + *remove = !IsDefinedClass(ctx, class_expr); + return true; +} + +static bool ClassFilterDataArrayOfObjects( + EvalContext *ctx, + const char *fn_name, + JsonElement *json_object, + const char *class_expr_key, + bool *remove) +{ + JsonElement *json_child = JsonObjectGet(json_object, class_expr_key); + if (json_child == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression key '%s': Key not found", + fn_name, class_expr_key); + return false; + } + + if (JsonGetType(json_child) != JSON_TYPE_STRING) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression at key '%s': Expected type string", + fn_name, class_expr_key); + return false; + } + + const char *class_expr = JsonPrimitiveGetAsString(json_child); + assert(class_expr != NULL); + + *remove = !IsDefinedClass(ctx, class_expr); + return true; +} + +static bool ClassFilterDataArray( + EvalContext *ctx, + const char *fn_name, + const char *data_structure, + const char *key_or_index, + JsonElement *child, + bool *remove) +{ + switch (JsonGetType(child)) + { + case JSON_TYPE_ARRAY: + if (StringEqual(data_structure, "auto") || + StringEqual(data_structure, "array_of_arrays")) + { + return ClassFilterDataArrayOfArrays( + ctx, fn_name, child, key_or_index, remove); + } + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type array", + fn_name); + break; + + case JSON_TYPE_OBJECT: + if (StringEqual(data_structure, "auto") || + StringEqual(data_structure, "array_of_objects")) + { + return ClassFilterDataArrayOfObjects( + ctx, fn_name, child, key_or_index, remove); + } + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type object", + fn_name); + break; + + default: + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type", + fn_name); + break; + } + + return false; +} + +static FnCallResult FnCallClassFilterData( + EvalContext *ctx, + ARG_UNUSED Policy const *policy, + FnCall const *fp, + Rlist const *args) +{ + assert(ctx != NULL); + assert(fp != NULL); + assert(args != NULL); + assert(args->next != NULL); + assert(args->next->next != NULL); + + bool allocated = false; + JsonElement *parent = VarNameOrInlineToJson(ctx, fp, args, false, &allocated); + if (parent == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected parent element to be of container type array", + fp->name); + return FnFailure(); + } + + /* Currently only parent type array is supported */ + if (JsonGetType(parent) != JSON_TYPE_ARRAY) + { + JsonDestroyMaybe(parent, allocated); + return FnFailure(); + } + + if (!allocated) + { + parent = JsonCopy(parent); + assert(parent != NULL); + } + + const char *data_structure = RlistScalarValue(args->next); + const char *key_or_index = RlistScalarValue(args->next->next); + + /* Iterate through array backwards so we can avoid having to compute index + * offsets for each removed element */ + for (size_t index_plus_one = JsonLength(parent); index_plus_one > 0; index_plus_one--) + { + assert(index_plus_one > 0); + size_t index = index_plus_one - 1; + JsonElement *child = JsonArrayGet(parent, index); + assert(child != NULL); + + bool remove; + if (!ClassFilterDataArray(ctx, fp->name, data_structure, key_or_index, child, &remove)) + { + /* Error is already logged */ + JsonDestroy(parent); + return FnFailure(); + } + + if (remove) + { + JsonArrayRemoveRange(parent, index, index); + } + } + + return FnReturnContainerNoCopy(parent); +} + static FnCallResult FnCallClassFilterCsv(EvalContext *ctx, ARG_UNUSED Policy const *policy, FnCall const *fp, @@ -10371,6 +10554,14 @@ static const FnCallArg VALIDJSON_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg CLASSFILTERDATA_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"}, + {"array_of_arrays,array_of_objects,auto", CF_DATA_TYPE_OPTION, "Specify type of data structure"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Key or index of class expressions"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + static const FnCallArg CLASSFILTERCSV_ARGS[] = { {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"}, @@ -10849,7 +11040,7 @@ static const FnCallArg IS_DATATYPE_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; static const FnCallArg FIND_LOCAL_USERS_ARGS[] = -{ +{ {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter list"}, {NULL, CF_DATA_TYPE_NONE, NULL} }; @@ -10907,6 +11098,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), FnCallTypeNew("classesmatching", CF_DATA_TYPE_STRING_LIST, CLASSMATCH_ARGS, &FnCallClassesMatching, "List the defined classes matching regex arg1 and tag regexes arg2,arg3,...", FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("classfilterdata", CF_DATA_TYPE_CONTAINER, CLASSFILTERDATA_ARGS, &FnCallClassFilterData, "Filter data container by defined classes", + FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), FnCallTypeNew("classfiltercsv", CF_DATA_TYPE_CONTAINER, CLASSFILTERCSV_ARGS, &FnCallClassFilterCsv, "Parse a CSV file and create data container filtered by defined classes", FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL), FnCallTypeNew("countclassesmatching", CF_DATA_TYPE_INT, CLASSMATCH_ARGS, &FnCallClassesMatching, "Count the number of defined classes matching regex arg1", diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo.cf new file mode 100644 index 0000000000..43052db129 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo.cf @@ -0,0 +1,78 @@ +body common control +{ + bundlesequence => { "check" }; +} + +bundle agent test(context, index) +{ + meta: + "description" -> { "ENT-6193", "CFE-3421" } + string => "Test for expected results from policy function classfilterdata() with array of arrays using context '$(with)' and index $(index)", + with => join("', '", "context"); + + vars: + "test" + data => '[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + [ "bar", "!bar", "bar&baz", "bar|baz" ], + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'; + + "actual" + data => classfilterdata("@(test)", "array_of_arrays", "$(index)"); + + classes: + "$(context)"; + + reports: + "$(with)" + with => storejson("@(actual)"), + bundle_return_value_index => "$(index)"; +} + +bundle agent check +{ + vars: + "num_indices" string => "3"; + "context" slist => { "foo" }; + "range" slist => expandrange("[0-$(num_indices)]", "1"); + + "expected[0]" + string => storejson('[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + ]'); + "expected[1]" + string => storejson('[ + [ "bar", "!bar", "bar&baz", "bar|baz" ], + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'); + "expected[2]" + string => storejson('[ + ]'); + "expected[3]" + string => storejson('[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'); + + classes: + "ok_$(range)" + expression => strcmp("$(expected[$(range)])", + "$(actual[$(range)])"); + "ok" + expression => and(expandrange("ok_[0-$(num_indices)]", "1")); + + methods: + "context: '$(with)' and index: $(range)" + usebundle => test("@(context)", "$(range)"), + useresult => "actual", + with => join(", ", "context"); + + reports: + "Context '$(with)'; index $(range); expected '$(expected[$(range)])'; actual '$(actual[$(range)])'" + with => join("', '", "context"); + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo_bar.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo_bar.cf new file mode 100644 index 0000000000..0d2130fdf6 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_arrays_foo_bar.cf @@ -0,0 +1,80 @@ +body common control +{ + bundlesequence => { "check" }; +} + +bundle agent test(context, index) +{ + meta: + "description" -> { "ENT-6193", "CFE-3421" } + string => "Test for expected results from policy function classfilterdata() with array of arrays using context '$(with)' and index $(index)", + with => join("', '", "context"); + + vars: + "test" + data => '[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + [ "bar", "!bar", "bar&baz", "bar|baz" ], + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'; + + "actual" + data => classfilterdata("@(test)", "array_of_arrays", "$(index)"); + + classes: + "$(context)"; + + reports: + "$(with)" + with => storejson("@(actual)"), + bundle_return_value_index => "$(index)"; +} + +bundle agent check +{ + vars: + "num_indices" string => "3"; + "context" slist => { "foo", "bar" }; + "range" slist => expandrange("[0-$(num_indices)]", "1"); + + "expected[0]" + string => storejson('[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + [ "bar", "!bar", "bar&baz", "bar|baz" ], + ]'); + "expected[1]" + string => storejson('[ + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'); + "expected[2]" + string => storejson('[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + ]'); + "expected[3]" + string => storejson('[ + [ "foo", "!foo", "foo&bar", "foo|bar" ], + [ "bar", "!bar", "bar&baz", "bar|baz" ], + [ "baz", "!baz", "foo&baz", "foo|baz" ], + ]'); + + classes: + "ok_$(range)" + expression => strcmp("$(expected[$(range)])", + "$(actual[$(range)])"); + "ok" + expression => and(expandrange("ok_[0-$(num_indices)]", "1")); + + methods: + "context: '$(with)' and index: $(range)" + usebundle => test("@(context)", "$(range)"), + useresult => "actual", + with => join(", ", "context"); + + reports: + "Context '$(with)'; index $(range); expected '$(expected[$(range)])'; actual '$(actual[$(range)])'" + with => join("', '", "context"); + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo.cf new file mode 100644 index 0000000000..5e424e8952 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo.cf @@ -0,0 +1,78 @@ +body common control +{ + bundlesequence => { "check" }; +} + +bundle agent test(context, index) +{ + meta: + "description" -> { "ENT-6193", "CFE-3421" } + string => "Test for expected results from policy function classfilterdata() with array of objects using context '$(with)' and key 'key_$(index)'", + with => join("', '", "context"); + + vars: + "test" + data => '[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + { "key_0": "bar", "key_1": "!bar", "key_2": "bar&baz", "key_3": "bar|baz" }, + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'; + + "actual" + data => classfilterdata("@(test)", "array_of_objects", "key_$(index)"); + + classes: + "$(context)"; + + reports: + "$(with)" + with => storejson("@(actual)"), + bundle_return_value_index => "$(index)"; +} + +bundle agent check +{ + vars: + "num_indices" string => "3"; + "context" slist => { "foo" }; + "range" slist => expandrange("[0-$(num_indices)]", "1"); + + "expected[0]" + string => storejson('[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + ]'); + "expected[1]" + string => storejson('[ + { "key_0": "bar", "key_1": "!bar", "key_2": "bar&baz", "key_3": "bar|baz" }, + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'); + "expected[2]" + string => storejson('[ + ]'); + "expected[3]" + string => storejson('[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'); + + classes: + "ok_$(range)" + expression => strcmp("$(expected[$(range)])", + "$(actual[$(range)])"); + "ok" + expression => and(expandrange("ok_[0-$(num_indices)]", "1")); + + methods: + "context: '$(with)' and index: $(range)" + usebundle => test("@(context)", "$(range)"), + useresult => "actual", + with => join(", ", "context"); + + reports: + "Context '$(with)'; index $(range); expected '$(expected[$(range)])'; actual '$(actual[$(range)])'" + with => join("', '", "context"); + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo_bar.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo_bar.cf new file mode 100644 index 0000000000..42af249d78 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_array_of_objects_foo_bar.cf @@ -0,0 +1,80 @@ +body common control +{ + bundlesequence => { "check" }; +} + +bundle agent test(context, index) +{ + meta: + "description" -> { "ENT-6193", "CFE-3421" } + string => "Test for expected results from policy function classfilterdata() with array of objects using context '$(with)' and key 'key_$(index)'", + with => join("', '", "context"); + + vars: + "test" + data => '[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + { "key_0": "bar", "key_1": "!bar", "key_2": "bar&baz", "key_3": "bar|baz" }, + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'; + + "actual" + data => classfilterdata("@(test)", "array_of_objects", "key_$(index)"); + + classes: + "$(context)"; + + reports: + "$(with)" + with => storejson("@(actual)"), + bundle_return_value_index => "$(index)"; +} + +bundle agent check +{ + vars: + "num_indices" string => "3"; + "context" slist => { "foo", "bar" }; + "range" slist => expandrange("[0-$(num_indices)]", "1"); + + "expected[0]" + string => storejson('[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + { "key_0": "bar", "key_1": "!bar", "key_2": "bar&baz", "key_3": "bar|baz" }, + ]'); + "expected[1]" + string => storejson('[ + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'); + "expected[2]" + string => storejson('[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + ]'); + "expected[3]" + string => storejson('[ + { "key_0": "foo", "key_1": "!foo", "key_2": "foo&bar", "key_3": "foo|bar" }, + { "key_0": "bar", "key_1": "!bar", "key_2": "bar&baz", "key_3": "bar|baz" }, + { "key_0": "baz", "key_1": "!baz", "key_2": "foo&baz", "key_3": "foo|baz" }, + ]'); + + classes: + "ok_$(range)" + expression => strcmp("$(expected[$(range)])", + "$(actual[$(range)])"); + "ok" + expression => and(expandrange("ok_[0-$(num_indices)]", "1")); + + methods: + "context: '$(with)' and index: $(range)" + usebundle => test("@(context)", "$(range)"), + useresult => "actual", + with => join(", ", "context"); + + reports: + "Context '$(with)'; index $(range); expected '$(expected[$(range)])'; actual '$(actual[$(range)])'" + with => join("', '", "context"); + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_collecting.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_collecting.cf new file mode 100644 index 0000000000..71a470aa74 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_collecting.cf @@ -0,0 +1,83 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; +} + +bundle agent init +{ + files: + "$(G.testfile).json" + content => '[ + { + "file": "/tmp/foo", + "mode": "755", + "owner": "root", + "group": "root", + "ifvarclass": "role_1" + }, + { + "file": "/tmp/bar", + "mode": "600", + "owner": "root", + "group": "root", + "ifvarclass": "role_2" + }, + { + "file": "/tmp/baz", + "mode": "644", + "owner": "root", + "group": "root", + "ifvarclass": "(role_1|role_2)" + } +]'; +} + +bundle agent test +{ + meta: + "description" -> { "ENT-6193", "CFE-3421" } + string => "Test that classfilterdata() is a collecting policy function"; + + classes: + "role_2"; + + vars: + "filtered" + data => classfilterdata(readjson("$(G.testfile).json"), "auto", "ifvarclass"); +} + +bundle agent check +{ + vars: + "expected" + string => storejson('[ + { + "file": "/tmp/bar", + "mode": "600", + "owner": "root", + "group": "root", + "ifvarclass": "role_2" + }, + { + "file": "/tmp/baz", + "mode": "644", + "owner": "root", + "group": "root", + "ifvarclass": "(role_1|role_2)" + } +]'); + "actual" + string => storejson("test.filtered"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + "Expected '$(expected)', actual '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +}