Unlock the power of CFEngine with expert insights and get your burning policy questions.
Cody, Craig and Nick discuss and answer CFEngine policy questions submitted by users.
Video
The video recording is available on YouTube:
At the end of every webinar, we stop the recording for a nice and relaxed, off-the-record chat with attendees. Join the next webinar to not miss this discussion.
Questions
These are the questions and policy used during the episode but each question has a separate blog post that goes into more detail.
How can I get a list of specific key values from an array of objects in JSON
bundle agent __main__
{
vars:
"d" data => '[
{"name": "Aurora", "value": "1"},
{"name": "Orion", "value": "2"},
{"name": "Luna", "value": "3"},
{"name": "Phoenix", "value": "4"},
{"name": "Atlas", "value": "5"}
]';
"d_names"
slist => sort( maparray( "$(d[$(this.k)][name])",
d ), lex );
}
Output:
Variable name Variable value Meta tags Comment
default:main.d [{"name":"Aurora","value":"1"},{"name":"Orion","value":"2"},{"name":"Luna","value":"3"},{"name":"Phoenix","value":"4"},{"name":"Atlas","value":"5"}] source=promise
default:main.d_names {"Atlas","Aurora","Luna","Orion","Phoenix"} source=promise
Refactored to use an associative array.
bundle agent __main__
{
vars:
"d" data => '[
{"name": "Aurora", "value": "1"},
{"name": "Orion", "value": "2"},
{"name": "Luna", "value": "3"},
{"name": "Phoenix", "value": "4"},
{"name": "Atlas", "value": "5"}
]';
"di" slist => getindices( d );
"d_names[$(di)]"
string => "$(d[$(di)][name])";
"names" slist => sort( getvalues( d_names ), lex);
}
Output:
Variable name Variable value Meta tags Comment
default:main.d [{"name":"Aurora","value":"1"},{"name":"Orion","value":"2"},{"name":"Luna","value":"3"},{"name":"Phoenix","value":"4"},{"name":"Atlas","value":"5"}] source=promise
default:main.d_names[0] Aurora source=promise
default:main.d_names[1] Orion source=promise
default:main.d_names[2] Luna source=promise
default:main.d_names[3] Phoenix source=promise
default:main.d_names[4] Atlas source=promise
default:main.di {"0","1","2","3","4"} source=promise
default:main.names {"Atlas","Aurora","Luna","Orion","Phoenix"} source=promise
Refactoring to show additional flexibility building a data structure key by key.
bundle agent __main__
{
vars:
"d" data => '[
{"name": "Aurora", "value": "1"},
{"name": "Orion", "value": "2"},
{"name": "Luna", "value": "3"},
{"name": "Phoenix", "value": "4"},
{"name": "Atlas", "value": "5"}
]';
"di" slist => getindices( d );
"d_names[$(di)]"
string => "$(d[$(di)][name])",
if => isgreaterthan( "$(d[$(di)][value])", 3 );
"names" slist => sort( getvalues( d_names ), lex);
}
Variable name Variable value Meta tags Comment
default:main.d [{"name":"Aurora","value":"1"},{"name":"Orion","value":"2"},{"name":"Luna","value":"3"},{"name":"Phoenix","value":"4"},{"name":"Atlas","value":"5"}] source=promise
default:main.d_names[3] Phoenix source=promise
default:main.d_names[4] Atlas source=promise
default:main.di {"0","1","2","3","4"} source=promise
default:main.names {"Atlas","Phoenix"} source=promise
Read more in the related blog post.
How can I Test CFEngine Policy
bundle agent __main__
{
methods:
"init";
"test";
"check";
}
bundle agent init
{
reports: "$(this.bundle)";
}
bundle agent test
{
reports: "$(this.bundle)";
}
bundle agent check
{
reports: "$(this.bundle)";
}
Output:
R: init
R: test
R: check
Refactored to use lib/testing.cf for emitting JUnit.
body file control
{
# We need to include the policy which we are leveraging
inputs => {
"$(sys.libdir)/lib/testing.cf", # The testing library
"$(sys.libdir)/lib/files.cf", # contains copy_from => default:local_dcp, edit_line => default:lines_present
"$(sys.libdir)/lib/common.cf", # Note: files.cf loads common.cf which contains printfile => default:cat, but we include it explicitly since we are using it here
};
}
bundle agent __main__
{
methods:
"init";
"test";
"check";
"cleanup";
}
bundle agent init
# @brief Setup the stage for the test
{
files:
# Let's initialize our test file from our pre-recorded starting state. This
# is the file that we will make our promise against.
"/tmp/test-file.txt"
copy_from => default:local_dcp( "/tmp/starting-state.txt" );
}
bundle agent test
# @brief Excercise the policy we wish to test
{
files:
"/tmp/test-file.txt"
edit_line => default:lines_present( "Big ones, small ones, some as big as your head!" );
}
bundle agent check
# @brief Verify that the state is as we expect
{
vars:
"test_check_required_files"
slist => { "/tmp/test-file.txt", "/tmp/expected-end-state.txt" };
classes:
"edit_line_default_lines_present_ok"
if => filesexist( @(test_check_required_files) ),
scope => "namespace",
expression => returnszero( concat( "/usr/bin/diff",
" /tmp/test-file.txt",
" /tmp/expected-end-state.txt" ),
"noshell" );
methods:
# Register each passing test
"Register test for appending a missing line"
usebundle => testing_ok_if("edit_line_default_lines_present_ok",
"Test that bundle edit_line default:lines_present appends a line that is not present in the file",
"error: bundle edit_line default:lines_present did not produce the expected result",
"trace",
"jUnit");
# Write out the jUnit report
"jUnit Report"
usebundle => testing_junit_report( "/tmp/test-result.xml" );
reports:
inform_mode::
"The full xml report:"
printfile => default:cat( "/tmp/test-result.xml" );
}
bundle agent cleanup
# @brief Cleanup the files we used in our testing.
{
files:
"/tmp/test-file.txt.*"
delete => default:tidy;
}
info: Copied file '/tmp/starting-state.txt' to '/tmp/test-file.txt.cfnew' (mode '600')
info: Moved '/tmp/test-file.txt.cfnew' to '/tmp/test-file.txt'
info: Updated file '/tmp/test-file.txt' from 'localhost:/tmp/starting-state.txt'
info: Inserted the promised line 'Big ones, small ones, some as big as your head!' into '/tmp/test-file.txt' after locator
info: insert_lines promise 'Big ones, small ones, some as big as your head!' repaired
info: Edited file '/tmp/test-file.txt'
R: testing_ok_if: adding testing report for class edit_line_default_lines_present_ok at position 1
info: Updated rendering of '/tmp/test-result.xml' from mustache template '/home/nickanderson/.cfagent/inputs/lib/templates/junit.mustache'
R: testing_generic_report: report summary: counts = 1/1/0/0/0 failed = { }, passed = { "testing_edit_line_default_lines_present_ok" }, skipped = { }, todo = { }, failed = { }; tests = [{"tap_message":"ok Test that bundle edit_line default:lines_present appends a line that is not present in the file","test_message":"Test that bundle edit_line default:lines_present appends a line that is not present in the file","test_offset":1,"testcase":"edit_line_default_lines_present_ok"}]+[]+[]+[]
R: The full xml report:
R: <?xml version="1.0" encoding="UTF-8"?>
R: <testsuite tests="1" failures="0" timestamp="2023-07-27T15:40:29">
R:
R: <testcase name="edit_line_default_lines_present_ok">Test that bundle edit_line default:lines_present appends a line that is not present in the file</testcase>
R:
R:
R:
R:
R: </testsuite>
R:
R: <!-- not implemented (yet):
R: 1) errors: <error message="my error message">my crash report</error>
R: 2) STDOUT: <system-out>my STDOUT dump</system-out>
R: 3) STDERR: <system-err>my STDERR dump</system-err>
R: -->
info: Deleted file '/tmp/test-file.txt'
info: Deleted file '/tmp/test-file.txt.cf-before-edit'
Read more in the related blog post.
Links
- Connect on LinkedIn w/ Cody, Craig, or Nick
- All Episodes
- Core policy acceptance tests and framework
- lib/testing.cf in the Masterfiles Policy Framework