diff --git a/tests/acceptance/30_generic_methods/file_line_present_in_ini_section.cf b/tests/acceptance/30_generic_methods/file_line_present_in_ini_section.cf new file mode 100644 index 000000000..15564c873 --- /dev/null +++ b/tests/acceptance/30_generic_methods/file_line_present_in_ini_section.cf @@ -0,0 +1,297 @@ +####################################################### +# +# Test checking if a line is present in a section file +# +####################################################### + +bundle common acc_path +{ + vars: + "root" string => getenv("NCF_TESTS_ACCEPTANCE", 1024); +} + +body common control +{ + inputs => { "${acc_path.root}/default.cf.sub", "${acc_path.root}/default_ncf.cf.sub", "@{ncf_inputs.default_files}" }; + bundlesequence => { configuration, default("${this.promise_filename}") }; + version => "1.0"; +} + +####################################################### +bundle agent init +{ + vars: + "tmp" string => getenv("TEMP", 1024); + + ## REPAIRED + # Simple addition + "file[0]" string => "${tmp}/test0.ini"; + "section[0]" string => "section1"; + "line[0]" string => "bar"; + "status[0]" string => "repaired"; + "initial[0]" string => "[section1] +foo"; + "expected[0]" string => "[section1] +foo +bar"; + + # Simple addition in middle section + "file[1]" string => "${tmp}/test1.ini"; + "section[1]" string => "section1"; + "line[1]" string => "bar"; + "status[1]" string => "repaired"; + "initial[1]" string => "[section0] +some nice content +[section1] +foo +[section2] +foobar"; + "expected[1]" string => "[section0] +some nice content +[section1] +foo +bar +[section2] +foobar"; + + # Simple addition in middle section with line already present in other sections + "file[2]" string => "${tmp}/test2.ini"; + "section[2]" string => "section1"; + "line[2]" string => "bar"; + "status[2]" string => "repaired"; + "initial[2]" string => "[section0] +some nice content +bar +[section1] +foo +[section2] +foobar"; + "expected[2]" string => "[section0] +some nice content +bar +[section1] +foo +bar +[section2] +foobar"; + + # Simple addition with undefined section and content in other sections + "file[3]" string => "${tmp}/test3.ini"; + "section[3]" string => "new_section"; + "line[3]" string => "bar +foobar"; + "status[3]" string => "repaired"; + "initial[3]" string => "[section1] +foo +bar +foobar"; + "expected[3]" string => "[section1] +foo +bar +foobar +[new_section] +bar +foobar"; + + # Simple addition with empty last section + "file[4]" string => "${tmp}/test4.ini"; + "section[4]" string => "section1"; + "line[4]" string => "foo +foo +bar +foobar"; + "status[4]" string => "repaired"; + "initial[4]" string => "[section1]"; + "expected[4]" string => "[section1] +foo +bar +foobar"; + + ## SUCCESS (same than above) + "file[5]" string => "${tmp}/test5.ini"; + "section[5]" string => "section1"; + "line[5]" string => "bar"; + "status[5]" string => "success"; + "initial[5]" string => "[section1] +foo +bar"; + "expected[5]" string => "${initial[5]}"; + + "file[6]" string => "${tmp}/test6.ini"; + "section[6]" string => "section1"; + "line[6]" string => "bar"; + "status[6]" string => "success"; + "initial[6]" string => "[section0] +some nice content +[section1] +foo +bar +[section2] +foobar"; + "expected[6]" string => "${initial[6]}"; + + "file[7]" string => "${tmp}/test7.ini"; + "section[7]" string => "section1"; + "line[7]" string => "bar"; + "status[7]" string => "success"; + "initial[7]" string => "[section0] +some nice content +bar +[section1] +foo +bar +[section2] +foobar"; + "expected[7]" string => "${initial[7]}"; + + "file[8]" string => "${tmp}/test8.ini"; + "section[8]" string => "new_section"; + "line[8]" string => "bar +foobar"; + "status[8]" string => "success"; + "initial[8]" string => "[section1] +foo +[new_section] +bar +foobar"; + "expected[8]" string => "${initial[8]}"; + + "file[9]" string => "${tmp}/test9.ini"; + "section[9]" string => "section1"; + "line[9]" string => "foo +bar +foobar"; + "status[9]" string => "success"; + "initial[9]" string => "[section1] +foo +bar +foobar"; + "expected[9]" string => "${initial[9]}"; + + + ## Others + # In a non existing file + "file[10]" string => "${tmp}/test10.ini"; + "section[10]" string => "section1"; + "line[10]" string => "bar"; + "status[10]" string => "repaired"; + "expected[10]" string => "[section1] +bar"; + + # Add a section using the method + # then add a line under it + # The expected part is the same as the on in the next test + "file[11]" string => "${tmp}/test11.ini"; + "section[11]" string => "section1"; + "line[11]" string => "[section2]"; + "status[11]" string => "repaired"; + "initial[11]" string => "[section1]"; + "expected[11]" string => "[section1] +[section2] +foo"; + "file[12]" string => "${tmp}/test11.ini"; + "section[12]" string => "section2"; + "line[12]" string => "foo"; + "status[12]" string => "repaired"; + "initial[12]" string => "[section1]"; + "expected[12]" string => "[section1] +[section2] +foo"; + + # Simple addition with empty last section and existing content + "file[13]" string => "${tmp}/test13.ini"; + "section[13]" string => "section1"; + "line[13]" string => "foo +bar +foobar"; + "status[13]" string => "repaired"; + "initial[13]" string => "[section0] +foobar +bar +foo +[section1]"; + "expected[13]" string => "${initial[13]} +foo +bar +foobar"; + + "indices" slist => getindices("status"); + + files: + "${file[${indices}]}" + create => "true", + edit_line => insert_lines("${initial[${indices}]}"), + edit_defaults => empty, + unless => strcmp("${indices}", "10"); +} + +####################################################### + +bundle agent test +{ + vars: + "args${init.indices}" slist => { "${init.file[${init.indices}]}", "${init.section[${init.indices}]}", "${init.line[${init.indices}]}" }; + + methods: + # Enforce + "ph0" usebundle => apply_gm("file_line_present_in_ini_section", @{args0}, "${init.status[0]}", "ph0", "enforce" ); + "ph1" usebundle => apply_gm("file_line_present_in_ini_section", @{args1}, "${init.status[1]}", "ph1", "enforce" ); + "ph2" usebundle => apply_gm("file_line_present_in_ini_section", @{args2}, "${init.status[2]}", "ph2", "enforce" ); + "ph3" usebundle => apply_gm("file_line_present_in_ini_section", @{args3}, "${init.status[3]}", "ph3", "enforce" ); + "ph4" usebundle => apply_gm("file_line_present_in_ini_section", @{args4}, "${init.status[4]}", "ph4", "enforce" ); + "ph5" usebundle => apply_gm("file_line_present_in_ini_section", @{args5}, "${init.status[5]}", "ph5", "enforce" ); + "ph6" usebundle => apply_gm("file_line_present_in_ini_section", @{args6}, "${init.status[6]}", "ph6", "enforce" ); + "ph7" usebundle => apply_gm("file_line_present_in_ini_section", @{args7}, "${init.status[7]}", "ph7", "enforce" ); + "ph8" usebundle => apply_gm("file_line_present_in_ini_section", @{args8}, "${init.status[8]}", "ph8", "enforce" ); + "ph9" usebundle => apply_gm("file_line_present_in_ini_section", @{args9}, "${init.status[9]}", "ph9", "enforce" ); + "ph10" usebundle => apply_gm("file_line_present_in_ini_section", @{args10}, "${init.status[10]}", "ph10", "enforce" ); + "ph11" usebundle => apply_gm("file_line_present_in_ini_section", @{args11}, "${init.status[11]}", "ph11", "enforce" ); + "ph12" usebundle => apply_gm("file_line_present_in_ini_section", @{args12}, "${init.status[12]}", "ph12", "enforce" ); + "ph13" usebundle => apply_gm("file_line_present_in_ini_section", @{args13}, "${init.status[13]}", "ph13", "enforce" ); +} + +####################################################### + +bundle agent check +{ + vars: + pass1:: + "indices" slist => { @{init.indices} }; + # function readfile adds an extra trailing newline if there is no trailing newline, too inconsistent + "content[${indices}]" string => execresult("${paths.cat} ${init.file[${indices}]}", "noshell"); + + classes: + "pass3" expression => "pass2"; + "pass2" expression => "pass1"; + "pass1" expression => "any"; + + pass2:: + "content_ok_${indices}" expression => and( + strcmp("${content[${indices}]}", "${init.expected[${indices}]}"), + fileexists("${init.file[${indices}]}") + ); + + "content_not_ok" expression => "!content_ok_${indices}"; + "classes_ok" expression => "ph0_ok.ph1_ok.ph2_ok.ph3_ok.ph4_ok.ph5_ok.ph6_ok.ph7_ok.ph8_ok.ph9_ok.ph10_ok.ph11_ok.ph12_ok.ph13_ok"; + "ok" expression => "!content_not_ok.classes_ok"; + + + reports: + pass3:: + "########################### +ERROR test ${indices} in +${init.file[${indices}]} +EXPECTED: +${init.expected[${indices}]} +--------------------------- +FOUND: +${content[${indices}]} +###########################" + ifvarclass => "!content_ok_${indices}"; + + pass3.ok:: + "$(this.promise_filename) Pass"; + pass3.!ok:: + "$(this.promise_filename) FAIL"; +} + diff --git a/tests/acceptance/30_generic_methods/file_lines_present_in_ini_section.cf b/tests/acceptance/30_generic_methods/file_lines_present_in_ini_section.cf deleted file mode 100644 index ba4a631da..000000000 --- a/tests/acceptance/30_generic_methods/file_lines_present_in_ini_section.cf +++ /dev/null @@ -1,112 +0,0 @@ -####################################################### -# -# Test checking if a line is present in a section file -# -####################################################### - -bundle common acc_path -{ - vars: - "root" string => getenv("NCF_TESTS_ACCEPTANCE", 1024); -} - -body common control -{ - inputs => { "${acc_path.root}/default.cf.sub", "${acc_path.root}/default_ncf.cf.sub", "@{ncf_inputs.default_files}" }; - bundlesequence => { configuration, default("${this.promise_filename}") }; - version => "1.0"; -} - -####################################################### -bundle agent init -{ - vars: - "tmp" string => getenv("TEMP", 1024); - "file_1" string => "${tmp}/test1.ini"; - "reference_file_1" string => "${tmp}/ref1.ini"; - "file_1_canon" string => canonify("${file_1}"); - "file_2" string => "${tmp}/test2.ini"; - "reference_file_2" string => "${tmp}/ref2.ini"; - "file_2_canon" string => canonify("${file_2}"); - -# First test: ensure that a line is really added into the right section -# with lines already present. - "section_1" string => "section_test1"; - "line_1" string => "content"; - "base_text_up" string => "[section_test1] -This section as some irrelevant content"; - "base_text_down" string => "[section_test2]"; - "reference_1" string => "${base_text_up} -${line_1} -${base_text_down}"; - -# Second test: ensure that if the section does not exist, it will be created -# and the lines added - "section_2" string => "section_test2"; - "line_2" string => "another content"; - "reference_2" string => "${base_text_up} -${base_text_down} - -${line_2}"; - - commands: -# Initialize first test files - "/bin/echo" - args => "\"${reference_1}\" > \"${reference_file_1}\"", - contain => in_shell; - "/bin/echo" - args => "\"${base_text_up}\" > \"${file_1}\"", - contain => in_shell; - "/bin/echo" - args => "\"${base_text_down}\" >> \"${file_1}\"", - contain => in_shell; -# Initialize second test files - "/bin/echo" - args => "\"${reference_2}\" > \"${reference_file_2}\"", - contain => in_shell; - "/bin/echo" - args => "\"${base_text_up}\" > \"${file_2}\"", - contain => in_shell; - -} - -####################################################### - -bundle agent test -{ - methods: - "ph1" usebundle => file_line_present_in_ini_section("${init.file_1}", "${init.section_1}", "${init.line_1}"); - "ph2" usebundle => file_line_present_in_ini_section("${init.file_2}", "${init.section_2}", "${init.line_2}"); -} - -####################################################### - -bundle agent check -{ - vars: - # Commands to check that reference files (expected result) are the same - # than the modified files by the generic_method 'file_line_present_in_ini_section' - "line_1_exists_test" string => "/usr/bin/diff \"${init.reference_file_1}\" \"${init.file_1}\""; - "line_2_exists_test" string => "/usr/bin/diff \"${init.reference_file_2}\" \"${init.file_2}\""; - - classes: - "line_1_exists" - expression => returnszero("${line_1_exists_test}", "noshell"), - ifvarclass => canonify("file_line_present_in_ini_section_${init.file_1}_reached"); - "line_2_exists" - expression => returnszero("${line_2_exists_test}", "noshell"), - ifvarclass => canonify("file_line_present_in_ini_section_${init.file_2}_reached"); - - "ok_test1" expression => "line_1_exists.file_line_present_in_ini_section_${init.file_1_canon}_ok.!file_1_line_present_in_ini_section_${init.file_1_canon}_error"; - "ok_test2" expression => "line_2_exists.file_line_present_in_ini_section_${init.file_2_canon}_ok.!file_2_line_present_in_ini_section_${init.file_2_canon}_error"; - "ok" and => {"ok_test1","ok_test2"}; - - reports: - ok:: - "$(this.promise_filename) Pass"; - !ok:: - "$(this.promise_filename) FAIL"; - !line_1_exists:: - "diff command doesn't return 0 for command: ${line_1_exists_test}"; -} - diff --git a/tree/20_cfe_basics/ncf_lib.cf b/tree/20_cfe_basics/ncf_lib.cf index b47010b41..b0beef2c3 100644 --- a/tree/20_cfe_basics/ncf_lib.cf +++ b/tree/20_cfe_basics/ncf_lib.cf @@ -500,6 +500,13 @@ bundle edit_line ncf_insert_block(block) { comment => "Append a text block if it does not exist"; } +bundle edit_line append_block(block) { + insert_lines: + "${block}" + insert_type => "preserve_all_lines", + comment => "Append a text block to a file, not convergent"; +} + # Sets the RHS of configuration items with a given separator # supports keys that contains special characters (like *, ?) # This bundle will allow any quantity of spaces before the separator, but none diff --git a/tree/30_generic_methods/file_line_present_in_ini_section.cf b/tree/30_generic_methods/file_line_present_in_ini_section.cf index f72b1a3ff..8b85b23ef 100644 --- a/tree/30_generic_methods/file_line_present_in_ini_section.cf +++ b/tree/30_generic_methods/file_line_present_in_ini_section.cf @@ -34,32 +34,60 @@ bundle agent file_line_present_in_ini_section(file, section, line) "report_param" string => join("_", args); "full_class_prefix" string => canonify("file_line_present_in_ini_section_${report_param}"); "class_prefix" string => string_head("${full_class_prefix}", "1000"); - "section_and_blank_line" string => "[${section}] -"; + "hash" string => hash("${report_param}", "md5"); + "last_line" string => execresult("/usr/bin/tail -n 1 ${file} #${hash}", "useshell"); + + pass2:: + "line_list" slist => splitstring("${line}", "${const.n}", "999999"); + "unique_lines" slist => unique("line_list"); + "section_content" string => join("${const.n}", "unique_lines"); + "lines_to_insert" string => "[${section}]${const.n}${section_content}", + ifvarclass => "section_absent"; + "lines_to_insert" string => "${section_content}", + ifvarclass => "last_line_is_target_section"; classes: - # Check if the section exist: if not, a class will be raised to add it with a blank line. + "pass3" expression => "pass2"; + "pass2" expression => "pass1"; + "pass1" expression => "any"; + "section_absent" not => regline("^\[${section}\]$","${file}"); + "last_line_is_target_section" expression => regcmp("\[${section}\]\s*", "${last_line}"); files: - # If the section is not present in the file, firstly it will be added - # with a blank line in order to be catched for the lines to add after it. + pass3:: + # If the section is not present in the file, add it at the end + # Also, clean the doubled line in the input, since we can not + # limit the scope to the section, it does not exist yet "${file}" - create => "true", - edit_line => ncf_insert_block("${section_and_blank_line}"), + edit_line => append_block("${lines_to_insert}"), edit_defaults => ncf_empty_select("false"), + create => "true", ifvarclass => "section_absent", - comment => "Add section to file with a blank line"; + comment => "If the section or the file does not exist", + classes => classes_generic_two("${old_class_prefix}", "${class_prefix}"); + + # If the section is empty and the last one, use a different insert method + # This is due to a limitation issue on the select_region body + # see https://github.com/cfengine/masterfiles/blob/master/tests/acceptance/lib/files/CFE-1710.cf#L29 + "${file}" + edit_line => append_block("${lines_to_insert}"), + edit_defaults => ncf_empty_select("false"), + ifvarclass => "last_line_is_target_section", + comment => "Add lines at the end of the file since the target section exists, is empty and is the last one in the file", + classes => classes_generic_two("${old_class_prefix}", "${class_prefix}"); # Add the missing lines after the section. "${file}" - create => "true", edit_line => ensure_line_in_ini_section("${section}", "${line}"), edit_defaults => ncf_empty_select("false"), + ifvarclass => "!section_absent.!last_line_is_target_section", classes => classes_generic_two("${old_class_prefix}", "${class_prefix}"); methods: + pass3:: "sanitize" usebundle => _classes_sanitize("${class_prefix}"); "sanitize" usebundle => _classes_sanitize("${old_class_prefix}"); "report" usebundle => _log_v3("Insert line(s) into ${file}", "${file}", "${old_class_prefix}", "${class_prefix}", @{args}); } +