diff --git a/examples/classfilterdata_object_of_arrays.cf b/examples/classfilterdata_object_of_arrays.cf new file mode 100644 index 0000000000..725b289cc5 --- /dev/null +++ b/examples/classfilterdata_object_of_arrays.cf @@ -0,0 +1,37 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '{ + "alice": [ "role_1", 32 ], + "bob": [ "!role_1", 24 ], + "malcom": [ "role_2", 27 ] + }'; + + "filtered" + data => classfilterdata("original", "object_of_arrays", "0"); + + reports: + "Filtered data: $(with)" + with => storejson("filtered"); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Filtered data: { +#@ "bob": [ +#@ "!role_1", +#@ 24 +#@ ], +#@ "malcom": [ +#@ "role_2", +#@ 27 +#@ ] +#@ } +#@ ``` +#+end_src diff --git a/examples/classfilterdata_object_of_arrays_key_to_obj.cf b/examples/classfilterdata_object_of_arrays_key_to_obj.cf new file mode 100644 index 0000000000..23b3f7ac2e --- /dev/null +++ b/examples/classfilterdata_object_of_arrays_key_to_obj.cf @@ -0,0 +1,38 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '{ + "role_1" : [ "alice", 32 ], + "!role_1" : [ "bob", 24 ], + "role_2" : [ "malcom", 27 ] + }'; + + # Using exogenous key (i.e., the key of the child array within the parent object) + "filtered" + data => classfilterdata("original", "object_of_arrays"); + + 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_object_of_objects.cf b/examples/classfilterdata_object_of_objects.cf new file mode 100644 index 0000000000..69ea750dc9 --- /dev/null +++ b/examples/classfilterdata_object_of_objects.cf @@ -0,0 +1,35 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '{ + "/tmp/foo": { "ifvarclass": "role_1" }, + "/tmp/bar": { "ifvarclass": "role_2" }, + "/tmp/baz": { "ifvarclass": "(role_1|role_2)" } + }'; + + "filtered" + data => classfilterdata("original", "object_of_objects", "ifvarclass"); + + reports: + "Filtered data: $(with)" + with => storejson("filtered"); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Filtered data: { +#@ "/tmp/bar": { +#@ "ifvarclass": "role_2" +#@ }, +#@ "/tmp/baz": { +#@ "ifvarclass": "(role_1|role_2)" +#@ } +#@ } +#@ ``` +#+end_src diff --git a/examples/classfilterdata_object_of_objects_key_to_obj.cf b/examples/classfilterdata_object_of_objects_key_to_obj.cf new file mode 100644 index 0000000000..3d4b96b2c3 --- /dev/null +++ b/examples/classfilterdata_object_of_objects_key_to_obj.cf @@ -0,0 +1,36 @@ +#+begin_src cfengine3 +bundle agent __main__ +{ + classes: + "role_2"; + + vars: + "original" + data => '{ + "role_1": { "file": "/tmp/foo" }, + "role_2": { "file": "/tmp/bar" }, + "(role_1|role_2)": { "file": "/tmp/baz" } + }'; + + # Using exogenous key (i.e., the key of the child object within the parent object) + "filtered" + data => classfilterdata("original", "object_of_objects"); + + reports: + "Filtered data: $(with)" + with => storejson("filtered"); +} +#+end_src + +#+begin_src example_output +#@ ``` +#@ R: Filtered data: { +#@ "(role_1|role_2)": { +#@ "file": "/tmp/baz" +#@ }, +#@ "role_2": { +#@ "file": "/tmp/bar" +#@ } +#@ } +#@ ``` +#+end_src diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index c15d6104ae..4380eb4980 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -22,6 +22,7 @@ included file COSL.txt. */ +#include #ifdef __sun #define _POSIX_PTHREAD_SEMANTICS /* Required on Solaris 11 (see ENT-13146) */ #endif /* __sun */ @@ -7784,42 +7785,84 @@ static int JsonPrimitiveComparator(JsonElement const *left_obj, return StringSafeCompare(left, right); } -static bool ClassFilterDataArrayOfArrays( - EvalContext *ctx, +static const char *ClassFilterDataGetClassExprFromKey( + const char *fn_name, + JsonElement *json_object, + const char *key_to_class_expr) +{ + JsonElement *json_child = JsonObjectGet(json_object, key_to_class_expr); + if (json_child == NULL) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Specified key '%s' to class expression was not found in object", + fn_name, key_to_class_expr); + return NULL; + } + + if (JsonGetType(json_child) != JSON_TYPE_STRING) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Bad class expression found at given key '%s': " + "Expected type string, found %s", fn_name, key_to_class_expr, + JsonGetTypeAsString(json_child)); + return NULL; + } + + const char *class_expr = JsonPrimitiveGetAsString(json_child); + return class_expr; +} + +static const char *ClassFilterDataGetClassExprFromIndex( const char *fn_name, JsonElement *json_array, - const char *class_expr_index, - bool *remove) + const char *class_expr_index) { 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; + "Function %s(): Bad index '%s' to class expression: " + "Failed to parse integer", fn_name, class_expr_index); + return NULL; } 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)", + "Function %s(): Bad index '%s' to class expression: " + "Index out of bounds (%zu >= %zu)", fn_name, class_expr_index, index, length); - return false; + return NULL; } 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; + "Function %s(): Bad class expression found at given index '%zu': " + "Expected type string, found type %s", + fn_name, index, JsonGetTypeAsString(json_array)); + return NULL; } const char *class_expr = JsonPrimitiveGetAsString(json_child); - assert(class_expr != NULL); + return class_expr; +} + +static bool ClassFilterDataArrayOfArrays( + EvalContext *ctx, + const char *fn_name, + JsonElement *json_array, + const char *class_expr_index, + bool *remove) +{ + const char *class_expr = ClassFilterDataGetClassExprFromIndex(fn_name, json_array, class_expr_index); + if (class_expr == NULL) + { + /* Error is already logged */ + return false; + } *remove = !IsDefinedClass(ctx, class_expr); return true; @@ -7829,29 +7872,16 @@ static bool ClassFilterDataArrayOfObjects( EvalContext *ctx, const char *fn_name, JsonElement *json_object, - const char *class_expr_key, + const char *key_to_class_expr, 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) + const char *class_expr = ClassFilterDataGetClassExprFromKey(fn_name, json_object, key_to_class_expr); + if (class_expr == NULL) { - Log(LOG_LEVEL_VERBOSE, - "Function %s(): Bad class expression at key '%s': Expected type string", - fn_name, class_expr_key); + /* Error is already logged */ return false; } - const char *class_expr = JsonPrimitiveGetAsString(json_child); - assert(class_expr != NULL); - *remove = !IsDefinedClass(ctx, class_expr); return true; } @@ -7864,6 +7894,13 @@ static bool ClassFilterDataArray( JsonElement *child, bool *remove) { + if (key_or_index == NULL) { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): At least 3 arguments are required when the data structure is an array of objects or an array of arrays. " + "Please supply a key or index as the third argument.", fn_name); + return false; + } + switch (JsonGetType(child)) { case JSON_TYPE_ARRAY: @@ -7874,8 +7911,8 @@ static bool ClassFilterDataArray( 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); + "Function %s(): Expected child element to be of container type array, found %s", + fn_name, JsonGetTypeAsString(child)); break; case JSON_TYPE_OBJECT: @@ -7886,79 +7923,243 @@ static bool ClassFilterDataArray( 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); + "Function %s(): Expected child element to be of container type object, found %s", + fn_name, JsonGetTypeAsString(child)); break; default: Log(LOG_LEVEL_VERBOSE, - "Function %s(): Expected child element to be of container type", - fn_name); + "Function %s(): Expected child element to be of container type, found %s", + fn_name, JsonGetTypeAsString(child)); break; } return false; } +static bool ClassFilterDataObjectOfArrays( + EvalContext *ctx, + const char *fn_name, + JsonElement *json_array, + const char *index_to_class_expr, + const char *key_to_obj, + bool *remove) +{ + /* Use the key of the object itself as the class expression, unless a class + * expression index is specified */ + const char *class_expr = key_to_obj; + if (index_to_class_expr != NULL) + { + class_expr = ClassFilterDataGetClassExprFromIndex(fn_name, json_array, index_to_class_expr); + if (class_expr == NULL) + { + /* Error is already logged */ + return false; + } + } + + assert(class_expr != NULL); + *remove = !IsDefinedClass(ctx, class_expr); + + return true; +} + +static bool ClassFilterDataObjectOfObjects( + EvalContext *ctx, + const char *fn_name, + JsonElement *json_object, + const char *key_to_class_expr, + const char *key_to_obj, + bool *remove) +{ + /* Use the key of the object itself as the class expression, unless a class + * expression key is specified */ + const char *class_expr = key_to_obj; + if (key_to_class_expr != NULL) + { + class_expr = ClassFilterDataGetClassExprFromKey(fn_name, json_object, key_to_class_expr); + if (class_expr == NULL) + { + /* Error is already logged */ + return false; + } + } + + assert(class_expr != NULL); + *remove = !IsDefinedClass(ctx, class_expr); + + return true; +} + +static bool ClassFilterDataObject( + EvalContext *ctx, + const char *fn_name, + const char *data_structure, + const char *key_or_index, + JsonElement *child, + const char *key_to_obj, + bool *remove) +{ + switch (JsonGetType(child)) + { + case JSON_TYPE_ARRAY: + if (StringEqual(data_structure, "auto") || + StringEqual(data_structure, "object_of_arrays")) + { + return ClassFilterDataObjectOfArrays( + ctx, fn_name, child, key_or_index, key_to_obj, remove); + } + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type array, found %s", + fn_name, JsonGetTypeAsString(child)); + break; + + case JSON_TYPE_OBJECT: + if (StringEqual(data_structure, "auto") || + StringEqual(data_structure, "object_of_objects")) + { + return ClassFilterDataObjectOfObjects( + ctx, fn_name, child, key_or_index, key_to_obj, remove); + } + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type object, found %s", + fn_name, JsonGetTypeAsString(child)); + break; + + default: + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected child element to be of container type, found %s", + fn_name, JsonGetTypeAsString(child)); + break; + } + + return true; +} + static FnCallResult FnCallClassFilterData( EvalContext *ctx, ARG_UNUSED Policy const *policy, - FnCall const *fp, - Rlist const *args) + const FnCall *fp, + const Rlist *args) { assert(ctx != NULL); assert(fp != NULL); + + int n_args = 0; + for (const Rlist *arg = args; arg != NULL; arg = arg->next) + { + n_args += 1; + } + + if ((n_args < 2) || (n_args > 3)) + { + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Require 2 to 3 arguments, got %d", + fp->name, n_args); + return FnFailure(); + } + + /* CodeQL does not understand that this is implied from above */ 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); + "Function %s(): Expected parent element to be of container type array, found %s", + fp->name, JsonGetTypeAsString(parent)); return FnFailure(); } if (!allocated) { + /* Create heap allocated copy so that we can mutate it */ parent = JsonCopy(parent); assert(parent != NULL); } + /* This argument can hold one of: + * - 'array_of_arrays' + * - 'array_of_objects' + * - 'object_of_arrays' + * - 'object_of_objects' + * - 'auto' (to automatically detect the data structure) + */ 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); + /* This argument can hold: + * - the index of the class expression in a child array + * - the key to the class expression in a child object + * - or NULL (the class expression is the key to the child object) + */ + const char *key_or_index = (args->next->next != NULL) + ? RlistScalarValue(args->next->next) + : NULL; + + JsonIterator iter; + JsonElement *child; + switch (JsonGetType(parent)) { + case JSON_TYPE_ARRAY: + /* Iterate through array in backwards order so that 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; + 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 */ + 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); + } + } + break; + case JSON_TYPE_OBJECT: + iter = JsonIteratorInit(parent); + child = JsonIteratorNextValue(&iter); + + while ((child != NULL)) + { + const char *key_to_obj = JsonIteratorCurrentKey(&iter); + assert(key_to_obj != NULL); + + bool remove; + if (!ClassFilterDataObject(ctx, fp->name, data_structure, key_or_index, child, key_to_obj, &remove)) + { + /* Error is already logged */ + JsonDestroy(parent); + return FnFailure(); + } + + if (remove) + { + NDEBUG_UNUSED bool success = JsonObjectRemoveKey(parent, key_to_obj); + assert(success); + child = JsonIteratorCurrentValue(&iter); + } + else + { + child = JsonIteratorNextValue(&iter); + } + } + break; + default: + Log(LOG_LEVEL_VERBOSE, + "Function %s(): Expected parent element to be of container type array or object, found %s", + fp->name, JsonGetTypeAsString(parent)); JsonDestroy(parent); return FnFailure(); - } - - if (remove) - { - JsonArrayRemoveRange(parent, index, index); - } } return FnReturnContainerNoCopy(parent); @@ -10561,7 +10762,7 @@ static const FnCallArg VALIDJSON_ARGS[] = 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"}, + {"array_of_arrays,array_of_objects,object_of_arrays,object_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} }; @@ -11103,7 +11304,7 @@ const FnCallType CF_FNCALL_TYPES[] = 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), + FNCALL_OPTION_COLLECTING | FNCALL_OPTION_VARARG, 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_object_of_arrays_index_in_obj.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_arrays_index_in_obj.cf new file mode 100644 index 0000000000..dd8d7231df --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_arrays_index_in_obj.cf @@ -0,0 +1,48 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-4562" } + string => "Test for expected results from classfilterdata() with object of arrays using index in object"; + + vars: + "test" + data => '{ + "/etc/chrony.conf": [ "0644", "root", "root", "foo" ], + "/etc/apt/sources.list": [ "644", "root", "root", "foo|bar" ], + "/etc/apt/apt.conf": [ "644", "root", "root", "bar" ] + }'; + + "actual" + data => classfilterdata("@(test)", "object_of_arrays", "3"); + + classes: + "foo"; +} + +bundle agent check +{ + vars: + "expected" + string => storejson('{ + "/etc/chrony.conf": [ "0644", "root", "root", "foo" ], + "/etc/apt/sources.list": [ "644", "root", "root", "foo|bar" ] + }'); + "actual" + string => storejson("@(test.actual)"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + "Expected: '$(expected)', actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_arrays_key_to_obj.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_arrays_key_to_obj.cf new file mode 100644 index 0000000000..f2ea2ead0d --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_arrays_key_to_obj.cf @@ -0,0 +1,48 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-4562" } + string => "Test for expected results from classfilterdata() with object of arrays using key to object"; + + vars: + "test" + data => '{ + "foo": [ "0644", "root", "root", "/etc/chrony.conf" ], + "foo|bar": [ "644", "root", "root", "/etc/apt/sources.list" ], + "bar": [ "644", "root", "root", "/etc/apt/apt.conf" ] + }'; + + "actual" + data => classfilterdata("@(test)", "object_of_arrays"); + + classes: + "bar"; +} + +bundle agent check +{ + vars: + "expected" + string => storejson('{ + "foo|bar": [ "644", "root", "root", "/etc/apt/sources.list" ], + "bar": [ "644", "root", "root", "/etc/apt/apt.conf" ] + }'); + "actual" + string => storejson("@(test.actual)"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + "Expected: '$(expected)', actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_in_obj.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_in_obj.cf new file mode 100644 index 0000000000..de1323bc5c --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_in_obj.cf @@ -0,0 +1,73 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-4562" } + string => "Test for expected results from classfilterdata() with object of objects using key in object"; + + vars: + "test" + data => '{ + "/etc/chrony.conf": { + "mode": "0644", + "owner": "root", + "group": "root", + "if": "foo" + }, + "/etc/apt/sources.list": { + "mode": "644", + "owner": "root", + "group": "root", + "if": "foo|bar" + }, + "/etc/apt/apt.conf": { + "mode": "644", + "owner": "root", + "group": "root", + "if": "bar" + } + }'; + + "actual" + data => classfilterdata("@(test)", "object_of_objects", "if"); + + classes: + "foo"; +} + +bundle agent check +{ + vars: + "expected" + string => storejson('{ + "/etc/chrony.conf": { + "mode": "0644", + "owner": "root", + "group": "root", + "if": "foo" + }, + "/etc/apt/sources.list": { + "mode": "644", + "owner": "root", + "group": "root", + "if": "foo|bar" + } + }'); + "actual" + string => storejson("@(test.actual)"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + "Expected: '$(expected)', actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_to_obj.cf b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_to_obj.cf new file mode 100644 index 0000000000..44abb20b80 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/classfilterdata_object_of_objects_key_to_obj.cf @@ -0,0 +1,73 @@ +body common control +{ + bundlesequence => { "test", "check" }; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-4562" } + string => "Test for expected results from classfilterdata() with object of objects using key to object"; + + vars: + "test" + data => '{ + "foo": { + "mode": "0644", + "owner": "root", + "group": "root", + "file": "/etc/chrony.conf" + }, + "foo|bar": { + "mode": "644", + "owner": "root", + "group": "root", + "file": "/etc/apt/sources.list" + }, + "bar": { + "mode": "644", + "owner": "root", + "group": "root", + "file": "/etc/apt/apt.conf" + } + }'; + + "actual" + data => classfilterdata("@(test)", "object_of_objects"); + + classes: + "bar"; +} + +bundle agent check +{ + vars: + "expected" + string => storejson('{ + "foo|bar": { + "mode": "644", + "owner": "root", + "group": "root", + "file": "/etc/apt/sources.list" + }, + "bar": { + "mode": "644", + "owner": "root", + "group": "root", + "file": "/etc/apt/apt.conf" + } + }'); + "actual" + string => storejson("@(test.actual)"); + + classes: + "ok" + expression => strcmp("$(expected)", "$(actual)"); + + reports: + "Expected: '$(expected)', actual: '$(actual)'"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +}