1# Files bodies 2 3bundle common files_common 4# @brief Enumerate policy files used by this policy file for inclusion to inputs 5{ 6 vars: 7 "inputs" slist => { "$(this.promise_dirname)/common.cf" }; 8} 9 10body file control 11# @brief Include policy files used by this policy file as part of inputs 12{ 13 inputs => { @(files_common.inputs) }; 14} 15 16################################################### 17# edit_line bundles 18################################################### 19 20bundle edit_line insert_before_if_no_line(before, string) 21# @brief Insert `string` before `before` if `string` is not found in the file 22# @param before The regular expression matching the line which `string` will be 23# inserted before 24# @param string The string to be prepended 25# 26{ 27 insert_lines: 28 "$(string)" 29 location => before($(before)), 30 comment => "Prepend a line to the file if it doesn't already exist"; 31} 32 33## 34 35bundle edit_line insert_file(templatefile) 36# @brief Reads the lines from `templatefile` and inserts those into the 37# file being edited. 38# @param templatefile The name of the file from which to import lines. 39{ 40 insert_lines: 41 42 "$(templatefile)" 43 comment => "Insert the template file into the file being edited", 44 insert_type => "file"; 45} 46 47## 48 49bundle edit_line lines_present(lines) 50# @brief Ensure `lines` are present in the file. Lines that do not exist are appended to the file 51# @param lines List or string that should be present in the file 52# 53# **Example:** 54# 55# ```cf3 56# bundle agent example 57# { 58# vars: 59# "nameservers" slist => { "8.8.8.8", "8.8.4.4" }; 60# 61# files: 62# "/etc/resolv.conf" edit_line => lines_present( @(nameservers) ); 63# "/etc/ssh/sshd_config" edit_line => lines_present( "PermitRootLogin no" ); 64# } 65# ``` 66{ 67 insert_lines: 68 69 "$(lines)" 70 comment => "Append lines if they don't exist"; 71} 72 73bundle edit_line insert_lines(lines) 74# @brief Alias for `lines_present` 75# @param lines List or string that should be present in the file 76{ 77 insert_lines: 78 79 "$(lines)" 80 comment => "Append lines if they don't exist"; 81} 82 83bundle edit_line append_if_no_line(lines) 84# @brief Alias for `lines_present` 85# @param lines List or string that should be present in the file 86{ 87 insert_lines: 88 89 "$(lines)" 90 comment => "Append lines if they don't exist"; 91} 92 93bundle edit_line append_if_no_lines(lines) 94# @brief Alias for `lines_present` 95# @param lines List or string that should be present in the file 96{ 97 insert_lines: 98 99 "$(lines)" 100 comment => "Append lines if they don't exist"; 101} 102 103## 104 105bundle edit_line comment_lines_matching(regex,comment) 106# @brief Comment lines in the file that matching an [anchored] regex 107# @param regex Anchored regex that the entire line needs to match 108# @param comment A string that is prepended to matching lines 109{ 110 replace_patterns: 111 112 "^($(regex))$" 113 114 replace_with => comment("$(comment)"), 115 comment => "Search and replace string"; 116} 117 118## 119 120bundle edit_line contains_literal_string(string) 121# @brief Ensure the literal string is present in the promised file 122# @description If the string is not found in the file it is inserted according 123# to CFEngine defaults. 124# @param string The string (potentially multiline) to ensure exists in the 125# promised file. 126{ 127 128 insert_lines: 129 "$(string)" 130 insert_type => "preserve_block", 131 expand_scalars => "false", 132 whitespace_policy => { "exact_match" }; 133} 134 135## 136 137bundle edit_line uncomment_lines_matching(regex,comment) 138# @brief Uncomment lines of the file where the regex matches 139# the entire text after the comment string 140# @param regex The regex that lines need to match after `comment` 141# @param comment The prefix of the line that is removed 142{ 143 replace_patterns: 144 145 "^$(comment)\s?($(regex))$" 146 147 replace_with => uncomment, 148 comment => "Uncomment lines matching a regular expression"; 149} 150 151## 152 153bundle edit_line comment_lines_containing(regex,comment) 154# @brief Comment lines of the file matching a regex 155# @param regex A regex that a part of the line needs to match 156# @param comment A string that is prepended to matching lines 157{ 158 replace_patterns: 159 160 "^((?!$(comment)).*$(regex).*)$" 161 162 replace_with => comment("$(comment)"), 163 comment => "Comment out lines in a file"; 164} 165 166## 167 168bundle edit_line uncomment_lines_containing(regex,comment) 169# @brief Uncomment lines of the file where the regex matches 170# parts of the text after the comment string 171# @param regex The regex that lines need to match after `comment` 172# @param comment The prefix of the line that is removed 173{ 174 replace_patterns: 175 176 "^$(comment)\s?(.*$(regex).*)$" 177 178 replace_with => uncomment, 179 comment => "Uncomment a line containing a fragment"; 180} 181 182## 183 184bundle edit_line delete_lines_matching(regex) 185# @brief Delete lines matching a regular expression 186# @param regex The regular expression that the lines need to match 187{ 188 delete_lines: 189 190 "$(regex)" 191 192 comment => "Delete lines matching regular expressions"; 193} 194 195## 196 197bundle edit_line warn_lines_matching(regex) 198# @brief Warn about lines matching a regular expression 199# @param regex The regular expression that the lines need to match 200{ 201 delete_lines: 202 203 "$(regex)" 204 205 comment => "Warn about lines in a file", 206 action => warn_only; 207} 208 209## 210 211bundle edit_line prepend_if_no_line(string) 212# @brief Prepend `string` if it doesn't exist in the file 213# @param string The string to be prepended 214# 215# **See also:** [`insert_lines`][insert_lines] in 216# [`edit_line`][bundle edit_line] 217{ 218 insert_lines: 219 "$(string)" 220 location => start, 221 comment => "Prepend a line to the file if it doesn't already exist"; 222} 223 224## 225 226bundle edit_line replace_line_end(start,end) 227# @brief Give lines starting with `start` the ending given in `end` 228# 229# Whitespaces will be left unmodified. For example, 230# `replace_line_end("ftp", "2121/tcp")` would replace 231# 232# `"ftp 21/tcp"` 233# 234# with 235# 236# `"ftp 2121/tcp"` 237# 238# @param start The string lines have to start with 239# @param end The string lines should end with 240{ 241 field_edits: 242 243 "\s*$(start)\s.*" 244 comment => "Replace lines with $(this.start) and $(this.end)", 245 edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","set"); 246} 247 248bundle edit_line replace_uncommented_substrings( _comment, _find, _replace ) 249# @brief Replace all occurrences of `_find` with `_replace` on lines that do not follow a `_comment` 250# @param _comment Sequence of characters, each indicating the start of a comment. 251# @param _find String matching substring to replace 252# @param _replace String to substitute `_find` with 253# 254# **Example:** 255# 256# ```cf3 257# bundle agent example_replace_uncommented_substrings 258# { 259# files: 260# "/tmp/file.txt" 261# edit_line => replace_uncommented_substrings( "#", "ME", "YOU"); 262# } 263# ``` 264# 265# **Notes:** 266# 267# * Only single character comments are supported as `_comment` is used in the PCRE character group (`[^...]`). 268# * `-` in `_comment` is interpreted as a range unless it's used as the first or last character. For example, setting `_comment` to `0-9` means any digit starts a comment. 269# 270# **History:** 271# 272# * Introduced 3.17.0, 3.15.3 273{ 274 vars: 275 "_reg_match_uncommented_lines_containing_find" 276 string => "^([^$(_comment)]*)\Q$(_find)\E(.*$)"; 277 278 replace_patterns: 279 "$(_reg_match_uncommented_lines_containing_find)" 280 replace_with => text_between_match1_and_match2( $(_replace) ); 281} 282 283## 284 285bundle edit_line append_to_line_end(start,end) 286# @brief Append `end` to any lines beginning with `start` 287# 288# `end` will be appended to all lines starting with `start` and not 289# already ending with `end`. Whitespaces will be left unmodified. 290# 291# For example, `append_to_line_end("kernel", "vga=791")` would replace 292# `kernel /boot/vmlinuz root=/dev/sda7` 293# 294# with 295# 296# `kernel /boot/vmlinuz root=/dev/sda7 vga=791` 297# 298# **WARNING**: Be careful not to have multiple promises matching the same line, which would result in the line growing indefinitely. 299# 300# @param start pattern to match lines of interest 301# @param end string to append to matched lines 302# 303# **Example:** 304# 305# ```cf3 306# files: 307# "/tmp/boot-options" edit_line => append_to_line_end("kernel", "vga=791"); 308# ``` 309# 310{ 311 field_edits: 312 313 "\s*$(start)\s.*" 314 comment => "Append lines with $(this.start) and $(this.end)", 315 edit_field => line("(^|\s)$(start)\s*", "2", "$(end)","append"); 316} 317 318## 319 320bundle edit_line regex_replace(find,replace) 321# @brief Find exactly a regular expression and replace exactly the match with a string. 322# You can think of this like a PCRE powered sed. 323# @param find The regular expression 324# @param replace The replacement string 325{ 326 replace_patterns: 327 328 "$(find)" 329 replace_with => value("$(replace)"), 330 comment => "Search and replace string"; 331} 332 333## 334 335bundle edit_line resolvconf(search,list) 336# @brief Adds search domains and name servers to the system 337# resolver configuration. 338# 339# Use this bundle to modify `resolv.conf`. Existing entries for 340# `search` and `nameserver` are replaced. 341# 342# @param search The search domains with space 343# @param list An slist of nameserver addresses 344{ 345 delete_lines: 346 347 "search.*" comment => "Reset search lines from resolver"; 348 "nameserver.*" comment => "Reset nameservers in resolver"; 349 350 insert_lines: 351 352 "search $(search)" comment => "Add search domains to resolver"; 353 "nameserver $(list)" comment => "Add name servers to resolver"; 354} 355 356## 357 358bundle edit_line resolvconf_o(search,list,options) 359# @brief Adds search domains, name servers and options to the system 360# resolver configuration. 361# 362# Use this bundle to modify `resolv.conf`. Existing entries for 363# `search`, `nameserver` and `options` are replaced. 364# 365# @param search The search domains with space 366# @param list An slist of nameserver addresses 367# @param options is an slist of variables to modify the resolver 368 369{ 370 delete_lines: 371 372 "search.*" comment => "Reset search lines from resolver"; 373 "nameserver.*" comment => "Reset nameservers in resolver"; 374 "options.*" comment => "Reset options in resolver"; 375 376 insert_lines: 377 378 "search $(search)" comment => "Add search domains to resolver"; 379 "nameserver $(list)" comment => "Add name servers to resolver"; 380 "options $(options)" comment => "Add options to resolver"; 381} 382 383## 384 385bundle edit_line manage_variable_values_ini(tab, sectionName) 386# @brief Sets the RHS of configuration items in the file of the form 387# `LHS=RHS` 388# 389# If the line is commented out with `#`, it gets uncommented first. 390# Adds a new line if none exists. 391# Removes any variable value pairs not defined for the ini section. 392# 393# @param tab An associative array containing `tab[sectionName][LHS]="RHS"`. 394# The value is not changed when the `RHS` is "dontchange" 395# @param sectionName The section in the file within which values should be 396# modified 397# 398# **See also:** `set_variable_values_ini()` 399{ 400 vars: 401 "index" slist => getindices("$(tab)[$(sectionName)]"); 402 403 delete_lines: 404 ".*" 405 select_region => INI_section(escape("$(sectionName)")), 406 comment => "Remove all entries in the region so there are no extra entries"; 407 408 insert_lines: 409 "[$(sectionName)]" 410 location => start, 411 comment => "Insert lines"; 412 413 "$(index)=$($(tab)[$(sectionName)][$(index)])" 414 select_region => INI_section(escape("$(sectionName)")); 415} 416 417## 418 419bundle edit_line set_variable_values_ini(tab, sectionName) 420# @brief Sets the RHS of configuration items in the file of the form 421# `LHS=RHS` 422# 423# If the line is commented out with `#`, it gets uncommented first. 424# Adds a new line if none exists. 425# 426# @param tab An associative array containing `tab[sectionName][LHS]="RHS"`. 427# The value is not changed when the `RHS` is "dontchange" 428# @param sectionName The section in the file within which values should be 429# modified 430# 431# **See also:** `manage_variable_values_ini()` 432{ 433 vars: 434 "index" slist => getindices("$(tab)[$(sectionName)]"); 435 436 # Be careful if the index string contains funny chars 437 "cindex[$(index)]" string => canonify("$(index)"); 438 439 classes: 440 "edit_$(cindex[$(index)])" not => strcmp("$($(tab)[$(sectionName)][$(index)])","dontchange"), 441 comment => "Create conditions to make changes"; 442 443 field_edits: 444 445 # If the line is there, but commented out, first uncomment it 446 "#+\s*$(index)\s*=.*" 447 select_region => INI_section(escape("$(sectionName)")), 448 edit_field => col("\s*=\s*","1","$(index)","set"), 449 if => "edit_$(cindex[$(index)])"; 450 451 # match a line starting like the key something 452 "\s*$(index)\s*=.*" 453 edit_field => col("\s*=\s*","2","$($(tab)[$(sectionName)][$(index)])","set"), 454 select_region => INI_section(escape("$(sectionName)")), 455 classes => results("bundle", "set_variable_values_ini_not_$(cindex[$(index)])"), 456 if => "edit_$(cindex[$(index)])"; 457 458 insert_lines: 459 "[$(sectionName)]" 460 location => start, 461 comment => "Insert lines"; 462 463 "$(index)=$($(tab)[$(sectionName)][$(index)])" 464 select_region => INI_section(escape("$(sectionName)")), 465 if => "!(set_variable_values_ini_not_$(cindex[$(index)])_kept|set_variable_values_ini_not_$(cindex[$(index)])_repaired).edit_$(cindex[$(index)])"; 466 467} 468 469bundle edit_line insert_ini_section(name, config) 470# @brief Inserts a INI section with content 471# 472# ``` 473# # given an array "barray" 474# files: 475# "myfile.ini" edit_line => insert_ini_section("foo", "barray"); 476# ``` 477# 478# Inserts a section in an INI file with the given configuration 479# key-values from the array `config`. 480# 481# @param name the name of the INI section 482# @param config The fully-qualified name of an associative array containing `v[LHS]="rhs"` 483{ 484 vars: 485 # TODO: refactor once 3.7.x is EOL 486 "indices" slist => getindices($(config)); 487 "k" slist => sort("indices", lex); 488 489 insert_lines: 490 "[$(name)]" 491 location => start, 492 comment => "Insert an ini section with values if not present"; 493 494 "$(k)=$($(config)[$(k)])" 495 location => after("[$(name)]"); 496} 497 498 499bundle edit_line set_quoted_values(v) 500# @brief Sets the RHS of variables in shell-like files of the form: 501# 502# ``` 503# LHS="RHS" 504# ``` 505# 506# Adds a new line if no LHS exists, and replaces RHS values if one does exist. 507# If the line is commented out with #, it gets uncommented first. 508# 509# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` 510# 511# **Example:** 512# 513# ```cf3 514# vars: 515# "stuff[lhs-1]" string => "rhs1"; 516# "stuff[lhs-2]" string => "rhs2"; 517# 518# files: 519# "myfile" 520# edit_line => set_quoted_values(stuff) 521# ``` 522# 523# **See also:** `set_variable_values()` 524{ 525 meta: 526 "tags" 527 slist => 528 { 529 "deprecated=3.6.0", 530 "deprecation-reason=Generic reimplementation", 531 "replaced-by=set_line_based" 532 }; 533 534 vars: 535 "index" slist => getindices("$(v)"); 536 # Be careful if the index string contains funny chars 537 538 "cindex[$(index)]" string => canonify("$(index)"); 539 540 field_edits: 541 # If the line is there, but commented out, first uncomment it 542 "#+\s*$(index)\s*=.*" 543 edit_field => col("=","1","$(index)","set"); 544 545 # match a line starting like the key = something 546 "\s*$(index)\s*=.*" 547 edit_field => col("=","2",'"$($(v)[$(index)])"',"set"), 548 classes => results("bundle", "$(cindex[$(index)])_in_file"), 549 comment => "Match a line starting like key = something"; 550 551 insert_lines: 552 '$(index)="$($(v)[$(index)])"' 553 comment => "Insert a variable definition", 554 if => "!($(cindex[$(index)])_in_file_kept|$(cindex[$(index)])_in_file_repaired)"; 555} 556 557## 558 559bundle edit_line set_variable_values(v) 560# @brief Sets the RHS of variables in files of the form: 561# 562# ``` 563# LHS=RHS 564# ``` 565# 566# Adds a new line if no LHS exists, and replaces RHS values if one does exist. 567# If the line is commented out with #, it gets uncommented first. 568# 569# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` 570# 571# **Example:** 572# 573# ```cf3 574# vars: 575# "stuff[lhs-1]" string => "rhs1"; 576# "stuff[lhs-2]" string => "rhs2"; 577# 578# files: 579# "myfile" 580# edit_line => set_variable_values(stuff) 581# ``` 582# 583# **See also:** `set_quoted_values()` 584{ 585 meta: 586 "tags" 587 slist => 588 { 589 "deprecated=3.6.0", 590 "deprecation-reason=Generic reimplementation", 591 "replaced-by=set_line_based" 592 }; 593 594 vars: 595 596 "index" slist => getindices("$(v)"); 597 598 # Be careful if the index string contains funny chars 599 600 "cindex[$(index)]" string => canonify("$(index)"); 601 "cv" string => canonify("$(v)"); 602 603 field_edits: 604 605 # match a line starting like the key = something 606 607 "\s*$(index)\s*=.*" 608 609 edit_field => col("\s*$(index)\s*=","2","$($(v)[$(index)])","set"), 610 classes => results("bundle", "$(cv)_$(cindex[$(index)])_in_file"), 611 comment => "Match a line starting like key = something"; 612 613 insert_lines: 614 615 "$(index)=$($(v)[$(index)])" 616 617 comment => "Insert a variable definition", 618 if => "!($(cv)_$(cindex[$(index)])_in_file_kept|$(cv)_$(cindex[$(index)])_in_file_repaired)"; 619} 620 621bundle edit_line set_config_values(v) 622# @brief Sets the RHS of configuration items in the file of the form: 623# 624# ``` 625# LHS RHS 626# ``` 627# 628# If the line is commented out with `#`, it gets uncommented first. 629# 630# Adds a new line if none exists. 631# 632# @param v The fully-qualified name of an associative array containing `v[LHS]="rhs"` 633{ 634 meta: 635 "tags" 636 slist => 637 { 638 "deprecated=3.6.0", 639 "deprecation-reason=Generic reimplementation", 640 "replaced-by=set_line_based" 641 }; 642 643 vars: 644 "index" slist => getindices("$(v)"); 645 646 # Be careful if the index string contains funny chars 647 "cindex[$(index)]" string => canonify("$(index)"); 648 649 # Escape the value (had a problem with special characters and regex's) 650 "ev[$(index)]" string => escape("$($(v)[$(index)])"); 651 652 # Do we have more than one line commented out? 653 "index_comment_matches_$(cindex[$(index)])" 654 int => countlinesmatching("^\s*#\s*($(index)\s+.*|$(index))$","$(edit.filename)"); 655 656 657 classes: 658 # Check to see if this line exists 659 "line_exists_$(cindex[$(index)])" 660 expression => regline("^\s*($(index)\s.*|$(index))$","$(edit.filename)"), 661 scope => "bundle"; 662 663 # if there's more than one comment, just add new (don't know who to use) 664 "multiple_comments_$(cindex[$(index)])" 665 expression => isgreaterthan("$(index_comment_matches_$(cindex[$(index)]))","1"), 666 scope => "bundle"; 667 668 replace_patterns: 669 # If the line is commented out, uncomment and replace with 670 # the correct value 671 "^\s*#\s*($(index)\s+.*|$(index))$" 672 comment => "If we find a single commented entry we can uncomment it to 673 keep the settings near any inline documentation. If there 674 are multiple comments, then we don't try to replace them and 675 instead will later append the new value after the first 676 commented occurrence of $(index).", 677 handle => "set_config_values_replace_commented_line", 678 replace_with => value("$(index) $($(v)[$(index)])"), 679 if => "!line_exists_$(cindex[$(index)]).!replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])", 680 classes => results("bundle", "uncommented_$(cindex[$(index)])"); 681 682 # If the line is there with the wrong value, replace with 683 # the correct value 684 "^\s*($(index)\s+(?!$(ev[$(index)])$).*|$(index))$" 685 comment => "Correct the value $(index)", 686 replace_with => value("$(index) $($(v)[$(index)])"), 687 classes => results("bundle", "replace_attempted_$(cindex[$(index)])"); 688 689 insert_lines: 690 # If the line doesn't exist, or there is more than one occurrence 691 # of the LHS commented out, insert a new line and try to place it 692 # after the commented LHS (keep new line with old comments) 693 "$(index) $($(v)[$(index)])" 694 comment => "Insert the value, marker exists $(index)", 695 location => after("^\s*#\s*($(index)\s+.*|$(index))$"), 696 if => "replace_attempted_$(cindex[$(index)])_reached.multiple_comments_$(cindex[$(index)])"; 697 698 # If the line doesn't exist and there are no occurrences 699 # of the LHS commented out, insert a new line at the eof 700 "$(index) $($(v)[$(index)])" 701 comment => "Insert the value, marker doesn't exist $(index)", 702 if => "replace_attempted_$(cindex[$(index)])_reached.!multiple_comments_$(cindex[$(index)])"; 703 704} 705 706bundle edit_line set_line_based(v, sep, bp, kp, cp) 707# @brief Sets the RHS of configuration items in the file of the form: 708# 709# ``` 710# LHS$(sep)RHS 711# ``` 712# 713# Example usage for `x=y` lines (e.g. rsyncd.conf): 714# 715# ```cf3 716# "myfile" 717# edit_line => set_line_based("test.config", "=", "\s*=\s*", ".*", "\s*#\s*"); 718# ``` 719# 720# Example usage for `x y` lines (e.g. sshd_config): 721# 722# ```cf3 723# "myfile" 724# edit_line => set_line_based("test.config", " ", "\s+", ".*", "\s*#\s*"); 725# ``` 726# 727# If the line is commented out with `$(cp)`, it gets uncommented first. 728# 729# Adds a new line if none exists or if more than one commented-out 730# possible matches exist. 731# 732# 733# **Note:** If the data structure being used for the first parameter is in the current bundle, you can use `$(this.bundle).variable`. 734# 735# Originally `set_config_values` by Ed King. 736# 737# @param v The fully-qualified name (`bundlename.variable`) of an associative array containing `v[LHS]="rhs"` 738# @param sep The separator to insert, e.g. ` ` for space-separated 739# @param bp The key-value separation regex, e.g. `\s+` for space-separated 740# @param kp The keys to select from v, use `.*` for all 741# @param cp The comment pattern from line-start, e.g. `\s*#\s*` 742{ 743 meta: 744 "tags" 745 slist => 746 { 747 "replaces=set_config_values", 748 "replaces=set_config_values_matching", 749 "replaces=set_variable_values", 750 "replaces=set_quoted_values", 751 "replaces=maintain_key_values", 752 }; 753 754 vars: 755 "vkeys" slist => getindices("$(v)"); 756 "i" slist => grep($(kp), vkeys); 757 758 # Be careful if the index string contains funny chars 759 "ci[$(i)]" string => canonify("$(i)"); 760 761 # Escape the value (had a problem with special characters and regex's) 762 "ev[$(i)]" string => escape("$($(v)[$(i)])"); 763 764 # Do we have more than one line commented out? 765 "comment_matches_$(ci[$(i)])" 766 int => countlinesmatching("^$(cp)($(i)$(bp).*|$(i))$", 767 $(edit.filename)); 768 769 770 classes: 771 # Check to see if this line exists 772 "exists_$(ci[$(i)])" 773 expression => regline("^\s*($(i)$(bp).*|$(i))$", 774 $(edit.filename)); 775 776 # if there's more than one comment, just add new (don't know who to use) 777 "multiple_comments_$(ci[$(i)])" 778 expression => isgreaterthan("$(comment_matches_$(ci[$(i)]))", 779 "1"); 780 781 782 replace_patterns: 783 # If the line is commented out, uncomment and replace with 784 # the correct value 785 "^$(cp)($(i)$(bp).*|$(i))$" 786 comment => "Uncommented the value '$(i)'", 787 replace_with => value("$(i)$(sep)$($(v)[$(i)])"), 788 if => "!exists_$(ci[$(i)]).!replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)])", 789 classes => results("bundle", "uncommented_$(ci[$(i)])"); 790 791 # If the line is there with the wrong value, replace with 792 # the correct value 793 "^\s*($(i)$(bp)(?!$(ev[$(i)])$).*|$(i))$" 794 comment => "Correct the value '$(i)'", 795 replace_with => value("$(i)$(sep)$($(v)[$(i)])"), 796 classes => results("bundle", "replace_attempted_$(ci[$(i)])"); 797 798 insert_lines: 799 # If the line doesn't exist, or there is more than one occurrence 800 # of the LHS commented out, insert a new line and try to place it 801 # after the commented LHS (keep new line with old comments) 802 "$(i)$(sep)$($(v)[$(i)])" 803 comment => "Insert the value, marker '$(i)' exists", 804 location => after("^$(cp)($(i)$(bp).*|$(i))$"), 805 if => "replace_attempted_$(ci[$(i)])_reached.multiple_comments_$(ci[$(i)])"; 806 807 # If the line doesn't exist and there are no occurrences 808 # of the LHS commented out, insert a new line at the eof 809 "$(i)$(sep)$($(v)[$(i)])" 810 comment => "Insert the value, marker '$(i)' doesn't exist", 811 if => "replace_attempted_$(ci[$(i)])_reached.!multiple_comments_$(ci[$(i)]).!exists_$(ci[$(i)])"; 812 813 reports: 814 verbose_mode|EXTRA:: 815 "$(this.bundle): Line for '$(i)' exists" if => "exists_$(ci[$(i)])"; 816 "$(this.bundle): Line for '$(i)' does not exist" if => "!exists_$(ci[$(i)])"; 817} 818 819bundle edit_line set_config_values_matching(v,pat) 820# @brief Sets the RHS of configuration items in the file of the form 821# 822# ``` 823# LHS RHS 824# ``` 825# 826# If the line is commented out with `#`, it gets uncommented first. 827# Adds a new line if none exists. 828# 829# @param v the fully-qualified name of an associative array containing v[LHS]="rhs" 830# @param pat Only elements of `v` that match the regex `pat` are use 831{ 832 meta: 833 "tags" 834 slist => 835 { 836 "deprecated=3.6.0", 837 "deprecation-reason=Generic reimplementation", 838 "replaced-by=set_line_based" 839 }; 840 841 vars: 842 "allparams" slist => getindices("$(v)"); 843 "index" slist => grep("$(pat)", "allparams"); 844 845 # Be careful if the index string contains funny chars 846 "cindex[$(index)]" string => canonify("$(index)"); 847 848 replace_patterns: 849 # If the line is there, maybe commented out, uncomment and replace with 850 # the correct value 851 "^\s*($(index)\s+(?!$($(v)[$(index)])).*|# ?$(index)\s+.*)$" 852 comment => "Correct the value", 853 replace_with => value("$(index) $($(v)[$(index)])"), 854 classes => results("bundle", "replace_attempted_$(cindex[$(index)])"); 855 856 insert_lines: 857 "$(index) $($(v)[$(index)])" 858 if => "replace_attempted_$(cindex[$(index)])_reached"; 859 860} 861 862## 863 864bundle edit_line maintain_key_values(v,sep) 865# @brief Sets the RHS of configuration items with an giving separator 866# 867# Contributed by David Lee 868{ 869 meta: 870 "tags" 871 slist => 872 { 873 "deprecated=3.6.0", 874 "deprecation-reason=Generic reimplementation", 875 "replaced-by=set_line_based" 876 }; 877 878 vars: 879 "index" slist => getindices("$(v)"); 880 # Be careful if the index string contains funny chars 881 "cindex[$(index)]" string => canonify("$(index)"); 882 # Matching pattern for line (basically key-and-separator) 883 "keypat[$(index)]" string => "\s*$(index)\s*$(sep)\s*"; 884 885 # Values may contain regexps. Escape them for replace_pattern matching. 886 "ve[$(index)]" string => escape("$($(v)[$(index)])"); 887 888 classes: 889 "$(cindex[$(index)])_key_in_file" 890 comment => "Dynamic Class created if patterns matching", 891 expression => regline("^$(keypat[$(index)]).*", "$(edit.filename)"); 892 893 replace_patterns: 894 # For convergence need to use negative lookahead on value: 895 # "key sep (?!value).*" 896 "^($(keypat[$(index)]))(?!$(ve[$(index)])$).*" 897 comment => "Replace definition of $(index)", 898 replace_with => value("$(match.1)$($(v)[$(index)])"); 899 900 insert_lines: 901 "$(index)$(sep)$($(v)[$(index)])" 902 comment => "Insert definition of $(index)", 903 if => "!$(cindex[$(index)])_key_in_file"; 904} 905 906## 907 908bundle edit_line append_users_starting(v) 909# @brief For adding to `/etc/passwd` or `etc/shadow` 910# @param v An array `v[username] string => "line..."` 911# 912# **Note:** To manage local users with CFEngine 3.6 and later, 913# consider making `users` promises instead of modifying system files. 914{ 915 vars: 916 917 "index" slist => getindices("$(v)"); 918 919 classes: 920 921 "add_$(index)" not => userexists("$(index)"), 922 comment => "Class created if user does not exist"; 923 924 insert_lines: 925 926 "$($(v)[$(index)])" 927 928 comment => "Append users into a password file format", 929 if => "add_$(index)"; 930} 931 932## 933 934bundle edit_line append_groups_starting(v) 935# @brief For adding groups to `/etc/group` 936# @param v An array `v[groupname] string => "line..."` 937# 938# **Note:** To manage local users with CFEngine 3.6 and later, 939# consider making `users` promises instead of modifying system files. 940{ 941 vars: 942 943 "index" slist => getindices("$(v)"); 944 945 classes: 946 947 "add_$(index)" not => groupexists("$(index)"), 948 comment => "Class created if group does not exist"; 949 950 insert_lines: 951 952 "$($(v)[$(index)])" 953 954 comment => "Append users into a group file format", 955 if => "add_$(index)"; 956 957} 958 959## 960 961bundle edit_line set_colon_field(key,field,val) 962# @brief Set the value of field number `field` of the line whose 963# first field is `key` to the value `val`, in a colon-separated file. 964# @param key The value the first field has to match 965# @param field The field to be modified 966# @param val The new value of `field` 967{ 968 field_edits: 969 970 "$(key):.*" 971 972 comment => "Edit a colon-separated file, using the first field as a key", 973 edit_field => col(":","$(field)","$(val)","set"); 974} 975 976## 977 978bundle edit_line set_user_field(user,field,val) 979# @brief Set the value of field number "field" in a `:-field` 980# formatted file like `/etc/passwd` 981# @param user The user to be modified 982# @param field The field that should be modified 983# @param val The value for `field` 984# 985# **Note:** To manage local users with CFEngine 3.6 and later, 986# consider making `users` promises instead of modifying system files. 987{ 988 field_edits: 989 990 "$(user):.*" 991 992 comment => "Edit a user attribute in the password file", 993 edit_field => col(":","$(field)","$(val)","set"); 994} 995 996## 997 998bundle edit_line append_user_field(group,field,allusers) 999# @brief For adding users to to a file like `/etc/group` 1000# at field position `field`, comma separated subfields 1001# @param group The group to be modified 1002# @param field The field where users should be added 1003# @param allusers The list of users to add to `field` 1004# 1005# **Note:** To manage local users with CFEngine 3.6 and later, 1006# consider making `users` promises instead of modifying system files. 1007{ 1008 field_edits: 1009 1010 "$(group):.*" 1011 1012 comment => "Append users into a password file format", 1013 edit_field => col(":","$(field)","$(allusers)","alphanum"); 1014} 1015 1016## 1017 1018bundle edit_line expand_template(templatefile) 1019# @brief Read in the named text file and expand `$(var)` inside the file 1020# @param templatefile The name of the file 1021{ 1022 insert_lines: 1023 1024 "$(templatefile)" 1025 1026 insert_type => "file", 1027 comment => "Expand variables in the template file", 1028 expand_scalars => "true"; 1029} 1030 1031bundle edit_line replace_or_add(pattern,line) 1032# @brief Replace a pattern in a file with a single line. 1033# 1034# If the pattern is not found, add the line to the file. 1035# 1036# @param pattern The pattern that should be replaced 1037# The pattern must match the whole line (it is automatically 1038# anchored to the start and end of the line) to avoid 1039# ambiguity. 1040# @param line The line with which to replace matches of `pattern` 1041{ 1042 vars: 1043 "cline" string => canonify("$(line)"); 1044 "eline" string => escape("$(line)"); 1045 1046 replace_patterns: 1047 "^(?!$(eline)$)$(pattern)$" 1048 comment => "Replace a pattern here", 1049 replace_with => value("$(line)"), 1050 classes => results("bundle", "replace_$(cline)"); 1051 1052 insert_lines: 1053 "$(line)" 1054 if => "replace_$(cline)_reached"; 1055} 1056 1057bundle edit_line converge(marker, lines) 1058# @brief Converge `lines` marked with `marker` 1059# 1060# Any content marked with `marker` is removed, then `lines` are 1061# inserted. Every `line` should contain `marker`. 1062# 1063# @param marker The marker (not a regular expression; will be escaped) 1064# @param lines The lines to insert; all must contain `marker` 1065# 1066# **Example:** 1067# 1068# ```cf3 1069# bundle agent pam_d_su_include 1070# #@brief Ensure /etc/pam.d/su has includes configured properly 1071# { 1072# files: 1073# ubuntu:: 1074# "/etc/pam.d/su" 1075# edit_line => converge( "@include", "@include common-auth 1076# @include common-account 1077# @include common-session"); 1078# } 1079# ``` 1080# 1081# **History:** 1082# 1083# * Introduced in 3.6.0 1084{ 1085 vars: 1086 "regex" string => escape($(marker)); 1087 1088 delete_lines: 1089 ".*$(regex).*" comment => "Delete lines matching the marker"; 1090 insert_lines: 1091 "$(lines)" comment => "Insert the given lines"; 1092} 1093 1094bundle edit_line converge_prepend(marker, lines) 1095# @brief Converge `lines` marked with `marker` to start of content 1096# 1097# Any content marked with `marker` is removed, then `lines` are 1098# inserted at *start* of content. Every `line` should contain `marker`. 1099# 1100# @param marker The marker (not a regular expression; will be escaped) 1101# @param lines The lines to insert; all must contain `marker` 1102# 1103# **Example:** 1104# 1105# ```cf3 1106# bundle agent pam_d_su_session 1107# #@brief Ensure /etc/pam.d/su has session configured properly 1108# { 1109# files: 1110# ubuntu:: 1111# "/etc/pam.d/su" 1112# edit_line => converge_prepend( "session", "session required pam_env.so readenv=1 envfile=/etc/default/locale 1113# session optional pam_mail.so nopen 1114# session required pam_limits.so" ); 1115# } 1116# ``` 1117# 1118# **History:** 1119# 1120# * Introduced in 3.17.0, 3.15.3, 3.12.6 1121{ 1122 vars: 1123 "regex" string => escape($(marker)); 1124 1125 delete_lines: 1126 ".*$(regex).*" comment => "Delete lines matching the marker"; 1127 insert_lines: 1128 "$(lines)" location => start, comment => "Insert the given lines"; 1129} 1130 1131 1132bundle edit_line fstab_option_editor(method, mount, option) 1133# @brief Add or remove `/etc/fstab` options for a mount 1134# 1135# This bundle edits the options field of a mount. The `method` is a 1136# `field_operation` which can be `append`, `prepend`, `set`, `delete`, 1137# or `alphanum`. The option is OS-specific. 1138# 1139# @param method `field_operation` to apply 1140# @param mount the mount point 1141# @param option the option to add or remove 1142# 1143# **Example:** 1144# 1145# ```cf3 1146# files: 1147# "/etc/fstab" edit_line => fstab_option_editor("delete", "/", "acl"); 1148# "/etc/fstab" edit_line => fstab_option_editor("append", "/", "acl"); 1149# ``` 1150{ 1151 field_edits: 1152 "(?!#)\S+\s+$(mount)\s.+" 1153 edit_field => fstab_options($(option), $(method)); 1154} 1155 1156##------------------------------------------------------- 1157## editing bodies 1158##------------------------------------------------------- 1159 1160body edit_field fstab_options(newval, method) 1161# @brief Edit the options field in a fstab format 1162# @param newval the new option 1163# @param method `field_operation` to apply 1164# 1165# This body edits the options field in the fstab file format. The 1166# `method` is a `field_operation` which can be `append`, `prepend`, 1167# `set`, `delete`, or `alphanum`. The `newval` option is OS-specific. 1168# 1169# **Example:** 1170# 1171# ```cf3 1172# # from the `fstab_options_editor` 1173# field_edits: 1174# "(?!#)\S+\s+$(mount)\s.+" 1175# edit_field => fstab_options($(option), $(method)); 1176# ``` 1177{ 1178 field_separator => "\s+"; 1179 select_field => "4"; 1180 value_separator => ","; 1181 field_value => "$(newval)"; 1182 field_operation => "$(method)"; 1183} 1184 1185body edit_field quoted_var(newval,method) 1186# @brief Edit the quoted value of the matching line 1187# @param newval The new value 1188# @param method The method by which to edit the field 1189{ 1190 field_separator => "\""; 1191 select_field => "2"; 1192 value_separator => " "; 1193 field_value => "$(newval)"; 1194 field_operation => "$(method)"; 1195 extend_fields => "false"; 1196 allow_blank_fields => "true"; 1197} 1198 1199## 1200 1201body edit_field col(split,col,newval,method) 1202# @brief Edit tabluar data with comma-separated sub-values 1203# @param split The separator that defines columns 1204# @param col The (1-based) index of the value to change 1205# @param newval The new value 1206# @param method The method by which to edit the field 1207{ 1208 field_separator => "$(split)"; 1209 select_field => "$(col)"; 1210 value_separator => ","; 1211 field_value => "$(newval)"; 1212 field_operation => "$(method)"; 1213 extend_fields => "true"; 1214 allow_blank_fields => "true"; 1215} 1216 1217## 1218 1219body edit_field line(split,col,newval,method) 1220# @brief Edit tabular data with space-separated sub-values 1221# @param split The separator that defines columns 1222# @param col The (1-based) index of the value to change 1223# @param newval The new value 1224# @param method The method by which to edit the field 1225{ 1226 field_separator => "$(split)"; 1227 select_field => "$(col)"; 1228 value_separator => " "; 1229 field_value => "$(newval)"; 1230 field_operation => "$(method)"; 1231 extend_fields => "true"; 1232 allow_blank_fields => "true"; 1233} 1234 1235## 1236 1237body replace_with text_between_match1_and_match2( _text ) 1238# @brief Replace matched line with substituted string 1239# @param _text String to substitute between first and second match 1240{ 1241 replace_value => "$(match.1)$(_text)$(match.2)"; 1242 occurrences => "all"; 1243} 1244 1245body replace_with value(x) 1246# @brief Replace matching lines 1247# @param x The replacement string 1248{ 1249 replace_value => "$(x)"; 1250 occurrences => "all"; 1251} 1252 1253## 1254 1255body select_region INI_section(x) 1256# @brief Restrict the `edit_line` promise to the lines in section `[x]` 1257# @param x The name of the section in an INI-like configuration file 1258{ 1259 select_start => "\[$(x)\]\s*"; 1260 select_end => "\[.*\]\s*"; 1261 1262@if minimum_version(3.10) 1263 select_end_match_eof => "true"; 1264@endif 1265} 1266 1267##------------------------------------------------------- 1268## edit_defaults 1269##------------------------------------------------------- 1270 1271body edit_defaults std_defs 1272# @brief Standard definitions for `edit_defaults` 1273# Don't empty the file before editing starts and don't make a backup. 1274{ 1275 empty_file_before_editing => "false"; 1276 edit_backup => "false"; 1277 #max_file_size => "300000"; 1278} 1279 1280## 1281 1282body edit_defaults empty 1283# @brief Empty the file before editing 1284# 1285# No backup is made 1286{ 1287 empty_file_before_editing => "true"; 1288 edit_backup => "false"; 1289 #max_file_size => "300000"; 1290} 1291 1292## 1293 1294body edit_defaults no_backup 1295# @brief Don't make a backup of the file before editing 1296{ 1297 edit_backup => "false"; 1298} 1299 1300## 1301 1302body edit_defaults backup_timestamp 1303# @brief Make a timestamped backup of the file before editing 1304{ 1305 empty_file_before_editing => "false"; 1306 edit_backup => "timestamp"; 1307 #max_file_size => "300000"; 1308} 1309 1310##------------------------------------------------------- 1311## location 1312##------------------------------------------------------- 1313 1314body location start 1315# @brief Editing occurs before the matched line 1316{ 1317 before_after => "before"; 1318} 1319 1320## 1321 1322body location after(str) 1323# @brief Editing occurs after the line matching `str` 1324# @param str Regular expression matching the file line location 1325{ 1326 before_after => "after"; 1327 select_line_matching => "$(str)"; 1328} 1329 1330## 1331 1332body location before(str) 1333# @brief Editing occurs before the line matching `str` 1334# @param str Regular expression matching the file line location 1335{ 1336 before_after => "before"; 1337 select_line_matching => "$(str)"; 1338} 1339 1340##------------------------------------------------------- 1341## replace_with 1342##------------------------------------------------------- 1343 1344## 1345 1346body replace_with comment(c) 1347# @brief Comment all lines matching the pattern by preprending `c` 1348# @param c The prefix that comments out lines 1349{ 1350 replace_value => "$(c) $(match.1)"; 1351 occurrences => "all"; 1352} 1353 1354## 1355 1356body replace_with uncomment 1357# @brief Uncomment all lines matching the pattern by removing 1358# anything outside the matching string 1359{ 1360 replace_value => "$(match.1)"; 1361 occurrences => "all"; 1362} 1363 1364##------------------------------------------------------- 1365## copy_from 1366##------------------------------------------------------- 1367 1368body copy_from secure_cp(from,server) 1369# @brief Download a file from a remote server over an encrypted channel 1370# 1371# Only copy the file if it is different from the local copy, and verify 1372# that the copy is correct. 1373# 1374# @param from The location of the file on the remote server 1375# @param server The hostname or IP of the server from which to download 1376{ 1377 source => "$(from)"; 1378 servers => { "$(server)" }; 1379 compare => "digest"; 1380 encrypt => "true"; 1381 verify => "true"; 1382} 1383 1384## 1385 1386body copy_from remote_cp(from,server) 1387# @brief Download a file from a remote server. 1388# 1389# @param from The location of the file on the remote server 1390# @param server The hostname or IP of the server from which to download 1391{ 1392 servers => { "$(server)" }; 1393 source => "$(from)"; 1394 compare => "mtime"; 1395} 1396 1397## 1398 1399body copy_from remote_dcp(from,server) 1400# @brief Download a file from a remote server if it is different from the local copy. 1401# 1402# @param from The location of the file on the remote server 1403# @param server The hostname or IP of the server from which to download 1404# 1405# **See Also:** `local_dcp()` 1406{ 1407 servers => { "$(server)" }; 1408 source => "$(from)"; 1409 compare => "digest"; 1410} 1411 1412## 1413 1414body copy_from local_cp(from) 1415# @brief Copy a file if the modification time or creation time of the source 1416# file is newer (the default comparison mechanism). 1417# @param from The path to the source file. 1418# 1419# **Example:** 1420# 1421# ```cf3 1422# bundle agent example 1423# { 1424# files: 1425# "/tmp/file.bak" 1426# copy_from => local_cp("/tmp/file"); 1427# } 1428# ``` 1429# 1430# **See Also:** `local_dcp()` 1431{ 1432 source => "$(from)"; 1433} 1434 1435## 1436 1437body copy_from local_dcp(from) 1438# @brief Copy a local file if the hash on the source file differs. 1439# @param from The path to the source file. 1440# 1441# **Example:** 1442# 1443# ```cf3 1444# bundle agent example 1445# { 1446# files: 1447# "/tmp/file.bak" 1448# copy_from => local_dcp("/tmp/file"); 1449# } 1450# ``` 1451# 1452# **See Also:** `local_cp()`, `remote_dcp()` 1453{ 1454 source => "$(from)"; 1455 compare => "digest"; 1456} 1457 1458## 1459 1460body copy_from perms_cp(from) 1461# @brief Copy a local file and preserve file permissions on the local copy. 1462# 1463# @param from The path to the source file. 1464{ 1465 source => "$(from)"; 1466 preserve => "true"; 1467} 1468 1469body copy_from perms_dcp(from) 1470# @brief Copy a local file if it is different from the existing copy and 1471# preserve file permissions on the local copy. 1472# 1473# @param from The path to the source file. 1474{ 1475 source => "$(from)"; 1476 preserve => "true"; 1477 compare => "digest"; 1478} 1479 1480body copy_from backup_local_cp(from) 1481# @brief Copy a local file and keep a backup of old versions. 1482# 1483# @param from The path to the source file. 1484{ 1485 source => "$(from)"; 1486 copy_backup => "timestamp"; 1487} 1488 1489## 1490 1491body copy_from seed_cp(from) 1492# @brief Copy a local file if the file does not already exist, i.e. seed the 1493# placement 1494# @param from The path to the source file. 1495# 1496# **Example:** 1497# 1498# ```cf3 1499# bundle agent home_dir_init 1500# { 1501# files: 1502# "/home/mark.burgess/." 1503# copy_from => seed_cp("/etc/skel"), 1504# depth_search => recurse(inf), 1505# file_select => all, 1506# comment => "We want to be sure that the home directory has files that are 1507# present in the skeleton."; 1508# } 1509# ``` 1510{ 1511 source => "$(from)"; 1512 compare => "exists"; 1513} 1514 1515## 1516 1517body copy_from sync_cp(from,server) 1518# @brief Synchronize a file with a remote server. 1519# 1520# * If the file does not exist on the remote server then it should be purged. 1521# * Allow types to change (directories to files and vice versa). 1522# * The mode of the remote file should be preserved. 1523# * Files are compared using the default comparison (mtime or ctime). 1524# 1525# @param from The location of the file on the remote server 1526# @param server The hostname or IP of the server from which to download 1527# 1528# **Example**: 1529# 1530# ```cf3 1531# files: 1532# "/tmp/masterfiles/." 1533# copy_from => sync_cp( "/var/cfengine/masterfiles", $(sys.policy_server) ), 1534# depth_search => recurse(inf), 1535# file_select => all, 1536# comment => "Mirror masterfiles from the hub to a temporary directory"; 1537# ``` 1538# 1539# **See Also:** `dir_sync()`, `copyfrom_sync()` 1540{ 1541 servers => { "$(server)" }; 1542 source => "$(from)"; 1543 purge => "true"; 1544 preserve => "true"; 1545 type_check => "false"; 1546} 1547 1548## 1549 1550body copy_from no_backup_cp(from) 1551# @brief Copy a local file and don't make any backup of the previous version 1552# 1553# @param from The path to the source file. 1554{ 1555 source => "$(from)"; 1556 copy_backup => "false"; 1557} 1558 1559## 1560 1561body copy_from no_backup_dcp(from) 1562# @brief Copy a local file if contents have changed, and don't make any backup 1563# of the previous version 1564# 1565# @param from The path to the source file. 1566{ 1567 source => "$(from)"; 1568 copy_backup => "false"; 1569 compare => "digest"; 1570} 1571 1572## 1573 1574body copy_from no_backup_rcp(from,server) 1575# @brief Download a file if it's newer than the local copy, and don't make any 1576# backup of the previous version 1577# 1578# @param from The location of the file on the remote server 1579# @param server The hostname or IP of the server from which to download 1580{ 1581 servers => { "$(server)" }; 1582 source => "$(from)"; 1583 compare => "mtime"; 1584 copy_backup => "false"; 1585} 1586 1587##------------------------------------------------------- 1588## link_from 1589##------------------------------------------------------- 1590 1591body link_from ln_s(x) 1592# @brief Create a symbolink link to `x` 1593# The link is created even if the source of the link does not exist. 1594# @param x The source of the link 1595{ 1596 link_type => "symlink"; 1597 source => "$(x)"; 1598 when_no_source => "force"; 1599} 1600 1601## 1602 1603body link_from linkchildren(tofile) 1604# @brief Create a symbolink link to `tofile` 1605# If the promiser is a directory, children are linked to the source, unless 1606# entries with identical names already exist. 1607# The link is created even if the source of the link does not exist. 1608# 1609# @param tofile The source of the link 1610{ 1611 source => "$(tofile)"; 1612 link_type => "symlink"; 1613 when_no_source => "force"; 1614 link_children => "true"; 1615 when_linking_children => "if_no_such_file"; # "override_file"; 1616} 1617 1618body link_from linkfrom(source, type) 1619# @brief Make any kind of link to a file 1620# @param source link to this 1621# @param type the link's type (`symlink` or `hardlink`) 1622{ 1623 source => $(source); 1624 link_type => $(type); 1625} 1626 1627##------------------------------------------------------- 1628## perms 1629##------------------------------------------------------- 1630 1631body perms m(mode) 1632# @brief Set the file mode 1633# @param mode The new mode 1634{ 1635 mode => "$(mode)"; 1636} 1637 1638## 1639 1640body perms mo(mode,user) 1641# @brief Set the file's mode and owners 1642# @param mode The new mode 1643# @param user The username of the new owner 1644{ 1645 owners => { "$(user)" }; 1646 mode => "$(mode)"; 1647} 1648 1649## 1650 1651body perms mog(mode,user,group) 1652# @brief Set the file's mode, owner and group 1653# @param mode The new mode 1654# @param user The username of the new owner 1655# @param group The group name 1656{ 1657 owners => { "$(user)" }; 1658 groups => { "$(group)" }; 1659 mode => "$(mode)"; 1660} 1661 1662## 1663 1664body perms og(u,g) 1665# @brief Set the file's owner and group 1666# @param u The username of the new owner 1667# @param g The group name 1668{ 1669 owners => { "$(u)" }; 1670 groups => { "$(g)" }; 1671} 1672 1673## 1674 1675body perms owner(user) 1676# @brief Set the file's owner 1677# @param user The username of the new owner 1678{ 1679 owners => { "$(user)" }; 1680} 1681 1682body perms system_owned(mode) 1683# @brief Set the file owner and group to the system default 1684# @param mode the access permission in octal format 1685# 1686# **Example:** 1687# 1688# ```cf3 1689# files: 1690# "/etc/passwd" perms => system_owned("0644"); 1691# ``` 1692{ 1693 mode => "$(mode)"; 1694 owners => { "root" }; 1695 1696 freebsd|openbsd|netbsd|darwin:: 1697 groups => { "wheel" }; 1698 1699 linux:: 1700 groups => { "root" }; 1701 1702 solaris:: 1703 groups => { "sys" }; 1704 1705 aix:: 1706 groups => { "system" }; 1707} 1708 1709##------------------------------------------------------- 1710## ACLS (extended Unix perms) 1711##------------------------------------------------------- 1712 1713body acl access_generic(acl) 1714# @brief Set the `aces` of the access control as specified 1715# 1716# Default/inherited ACLs are left unchanged. This body is 1717# applicable for both files and directories on all platforms. 1718# 1719# @param acl The aces to be set 1720{ 1721 acl_method => "overwrite"; 1722 aces => { "@(acl)" }; 1723 1724 windows:: 1725 acl_type => "ntfs"; 1726 1727 !windows:: 1728 acl_type => "posix"; 1729} 1730 1731## 1732 1733body acl ntfs(acl) 1734# @brief Set the `aces` on NTFS file systems, and overwrite 1735# existing ACLs. 1736# 1737# This body requires CFEngine Enterprise. 1738# 1739# @param acl The aces to be set 1740{ 1741 acl_type => "ntfs"; 1742 acl_method => "overwrite"; 1743 aces => { "@(acl)" }; 1744} 1745 1746## 1747 1748body acl strict 1749# @brief Limit file access via ACLs to users with administrator privileges, 1750# overwriting existing ACLs. 1751# 1752# **Note:** May need to take ownership of file/dir to be sure no-one else is 1753# allowed access. 1754{ 1755 acl_method => "overwrite"; 1756 1757 windows:: 1758 aces => { "user:Administrator:rwx" }; 1759 !windows:: 1760 aces => { "user:root:rwx" }; 1761} 1762 1763##------------------------------------------------------- 1764## depth_search 1765##------------------------------------------------------- 1766 1767body depth_search recurse(d) 1768# @brief Search files and direcories recursively, up to the specified depth 1769# Directories on different devices are excluded. 1770# 1771# @param d The maximum search depth 1772{ 1773 depth => "$(d)"; 1774 xdev => "true"; 1775} 1776 1777## 1778 1779body depth_search recurse_ignore(d,list) 1780# @brief Search files and directories recursively, 1781# but don't recurse into the specified directories 1782# 1783# @param d The maximum search depth 1784# @param list The list of directories to be excluded 1785{ 1786 depth => "$(d)"; 1787 exclude_dirs => { @(list) }; 1788} 1789 1790## 1791 1792body depth_search include_base 1793# @brief Search files and directories recursively, 1794# starting from the base directory. 1795{ 1796 include_basedir => "true"; 1797} 1798 1799body depth_search recurse_with_base(d) 1800# @brief Search files and directories recursively up to the specified 1801# depth, starting from the base directory excluding directories on 1802# other devices. 1803# 1804# @param d The maximum search depth 1805{ 1806 depth => "$(d)"; 1807 xdev => "true"; 1808 include_basedir => "true"; 1809} 1810 1811##------------------------------------------------------- 1812## delete 1813##------------------------------------------------------- 1814 1815body delete tidy 1816# @brief Delete the file and remove empty directories 1817# and links to directories 1818{ 1819 dirlinks => "delete"; 1820 rmdirs => "true"; 1821} 1822 1823##------------------------------------------------------- 1824## rename 1825##------------------------------------------------------- 1826 1827body rename disable 1828# @brief Disable the file 1829{ 1830 disable => "true"; 1831} 1832 1833## 1834 1835body rename rotate(level) 1836# @brief Rotate and store up to `level` backups of the file 1837# @param level The number of backups to store 1838{ 1839 rotate => "$(level)"; 1840} 1841 1842## 1843 1844body rename to(file) 1845# @brief Rename the file to `file` 1846# @param file The new name of the file 1847{ 1848 newname => "$(file)"; 1849} 1850 1851##------------------------------------------------------- 1852## file_select 1853##------------------------------------------------------- 1854 1855body file_select name_age(name,days) 1856# @brief Select files that have a matching `name` and have not been modified for at least `days` 1857# @param name A regex that matches the file name 1858# @param days Number of days 1859{ 1860 leaf_name => { "$(name)" }; 1861 mtime => irange(0,ago(0,0,"$(days)",0,0,0)); 1862 file_result => "mtime.leaf_name"; 1863} 1864 1865## 1866 1867body file_select days_old(days) 1868# @brief Select files that have not been modified for at least `days` 1869# @param days Number of days 1870{ 1871 mtime => irange(0,ago(0,0,"$(days)",0,0,0)); 1872 file_result => "mtime"; 1873} 1874 1875## 1876 1877body file_select size_range(from,to) 1878# @brief Select files that have a size within the specified range 1879# @param from The lower bound of the allowed file size 1880# @param to The upper bound of the allowed file size 1881{ 1882 search_size => irange("$(from)","$(to)"); 1883 file_result => "size"; 1884} 1885 1886## 1887 1888body file_select bigger_than(size) 1889# @brief Select files that are above a given size 1890# @param size The number of bytes files have 1891{ 1892 search_size => irange("0","$(size)"); 1893 file_result => "!size"; 1894} 1895 1896## 1897 1898body file_select exclude(name) 1899# @brief Select all files except those that match `name` 1900# @param name A regular expression 1901{ 1902 leaf_name => { "$(name)"}; 1903 file_result => "!leaf_name"; 1904} 1905 1906## 1907 1908body file_select plain 1909# @brief Select plain, regular files 1910{ 1911 file_types => { "plain" }; 1912 file_result => "file_types"; 1913} 1914 1915body file_select dirs 1916# @brief Select directories 1917{ 1918 file_types => { "dir" }; 1919 file_result => "file_types"; 1920} 1921 1922## 1923 1924body file_select by_name(names) 1925# @brief Select files that match `names` 1926# @param names A regular expression 1927{ 1928 leaf_name => { @(names)}; 1929 file_result => "leaf_name"; 1930} 1931 1932## 1933 1934body file_select ex_list(names) 1935# @brief Select all files except those that match `names` 1936# @param names A list of regular expressions 1937{ 1938 leaf_name => { @(names)}; 1939 file_result => "!leaf_name"; 1940} 1941 1942## 1943 1944body file_select all 1945# @brief Select all file system entries 1946{ 1947 leaf_name => { ".*" }; 1948 file_result => "leaf_name"; 1949} 1950 1951## 1952 1953body file_select older_than(years, months, days, hours, minutes, seconds) 1954# @brief Select files older than the date-time specified 1955# @param years Number of years 1956# @param months Number of months 1957# @param days Number of days 1958# @param hours Number of hours 1959# @param minutes Number of minutes 1960# @param seconds Number of seconds 1961# 1962# Generic older_than selection body, aimed to have a common definition handy 1963# for every case possible. 1964{ 1965 mtime => irange(0,ago("$(years)","$(months)","$(days)","$(hours)","$(minutes)","$(seconds)")); 1966 file_result => "mtime"; 1967} 1968 1969## 1970 1971body file_select filetype_older_than(filetype, days) 1972# @brief Select files of specified type older than specified number of days 1973# 1974# @param filetype File type to select 1975# @param days Number of days 1976# 1977# This body only takes a single filetype, see `filetypes_older_than()` 1978# if you want to select more than one type of file. 1979{ 1980 file_types => { "$(filetype)" }; 1981 mtime => irange(0,ago(0,0,"$(days)",0,0,0)); 1982 file_result => "file_types.mtime"; 1983} 1984 1985## 1986 1987body file_select filetypes_older_than(filetypes, days) 1988# @brief Select files of specified types older than specified number of days 1989# 1990# This body only takes a list of filetypes 1991# 1992# @param filetypes A list of file types 1993# @param days Number of days 1994# 1995# **See also:** `filetype_older_than()` 1996{ 1997 file_types => { @(filetypes) }; 1998 mtime => irange(0,ago(0,0,"$(days)",0,0,0)); 1999 file_result => "file_types.mtime"; 2000} 2001 2002body file_select symlinked_to(target) 2003# @brief Select symlinks that point to $(target) 2004# @param target The file the symlink should point to in order to be selected 2005{ 2006 file_types => { "symlink" }; 2007 issymlinkto => { "$(target)" }; 2008 file_result => "issymlinkto"; 2009} 2010 2011##------------------------------------------------------- 2012## changes 2013##------------------------------------------------------- 2014 2015body changes detect_all_change 2016# @brief Detect all file changes using the best hash method 2017# 2018# This is fierce, and will cost disk cycles 2019# 2020{ 2021 hash => "best"; 2022 report_changes => "all"; 2023 update_hashes => "yes"; 2024} 2025 2026## 2027 2028body changes detect_all_change_using(hash) 2029# @brief Detect all file changes using a given hash method 2030# 2031# Detect all changes using a configurable hashing algorithm 2032# for times when you care about both content and file stats e.g. mtime 2033# 2034# @param hash supported hashing algorithm (md5, sha1, sha224, sha256, sha384, sha512, best) 2035{ 2036 hash => "$(hash)"; 2037 report_changes => "all"; 2038 update_hashes => "yes"; 2039} 2040 2041## 2042 2043body changes detect_content 2044# @brief Detect file content changes using md5 2045# 2046# This is a cheaper alternative 2047{ 2048 hash => "md5"; 2049 report_changes => "content"; 2050 update_hashes => "yes"; 2051} 2052 2053## 2054 2055body changes detect_content_using(hash) 2056# @brief Detect file content changes using a given hash algorithm. 2057# 2058# For times when you only care about content, not file stats e.g. mtime 2059# @param hash - supported hashing algorithm (md5, sha1, sha224, sha256, sha384, 2060# sha512, best) 2061{ 2062 hash => "$(hash)"; 2063 report_changes => "content"; 2064 update_hashes => "yes"; 2065} 2066 2067## 2068 2069body changes noupdate 2070# @brief Detect content changes in (small) files that should never change 2071{ 2072 hash => "sha256"; 2073 report_changes => "content"; 2074 update_hashes => "no"; 2075} 2076 2077## 2078 2079body changes diff 2080# @brief Detect file content changes using sha256 2081# and report the diff to CFEngine Enterprise 2082{ 2083 hash => "sha256"; 2084 report_changes => "content"; 2085 report_diffs => "true"; 2086 update_hashes => "yes"; 2087} 2088 2089## 2090 2091body changes all_changes 2092# @brief Detect all file changes using sha256 2093# and report the diff to CFEngine Enterprise 2094{ 2095 hash => "sha256"; 2096 report_changes => "all"; 2097 report_diffs => "true"; 2098 update_hashes => "yes"; 2099} 2100 2101## 2102 2103body changes diff_noupdate 2104# @brief Detect content changes in (small) files 2105# and report the diff to CFEngine Enterprise 2106{ 2107 hash => "sha256"; 2108 report_changes => "content"; 2109 report_diffs => "true"; 2110 update_hashes => "no"; 2111} 2112 2113# template bundles 2114 2115bundle agent file_mustache(mustache_file, json_file, target_file) 2116# @brief Make a file from a Mustache template and a JSON file 2117# @param mustache_file the file with the Mustache template 2118# @param json_file a file with JSON data 2119# @param target_file the target file to write 2120# 2121# **Example:** 2122# 2123# ```cf3 2124# methods: 2125# "m" usebundle => file_mustache("x.mustache", "y.json", "z.txt"); 2126# ``` 2127{ 2128 files: 2129 "$(target_file)" 2130 create => "true", 2131 edit_template => $(mustache_file), 2132 template_data => readjson($(json_file), "100k"), 2133 template_method => "mustache"; 2134} 2135 2136bundle agent file_mustache_jsonstring(mustache_file, json_string, target_file) 2137# @brief Make a file from a Mustache template and a JSON string 2138# @param mustache_file the file with the Mustache template 2139# @param json_string a string with JSON data 2140# @param target_file the target file to write 2141# 2142# **Example:** 2143# 2144# ```cf3 2145# methods: 2146# "m" usebundle => file_mustache_jsonstring("x.mustache", '{ "x": "y" }', "z.txt"); 2147# ``` 2148{ 2149 files: 2150 "$(target_file)" 2151 create => "true", 2152 edit_template => $(mustache_file), 2153 template_data => parsejson($(json_string)), 2154 template_method => "mustache"; 2155} 2156 2157bundle agent file_tidy(file) 2158# @brief Remove a file 2159# @param file to remove 2160# 2161# **Example:** 2162# 2163# ```cf3 2164# methods: 2165# "" usebundle => file_tidy("/tmp/z.txt"); 2166# ``` 2167{ 2168 files: 2169 "$(file)" delete => tidy; 2170 2171 reports: 2172 "DEBUG|DEBUG_$(this.bundle)":: 2173 "DEBUG $(this.bundle): deleting $(file) with delete => tidy"; 2174} 2175 2176bundle agent dir_sync(from, to) 2177# @brief Synchronize a directory entire, deleting unknown files 2178# @param from source directory 2179# @param to destination directory 2180# 2181# **Example:** 2182# 2183# ```cf3 2184# methods: 2185# "" usebundle => dir_sync("/tmp", "/var/tmp"); 2186# ``` 2187{ 2188 files: 2189 "$(to)/." 2190 create => "true", 2191 depth_search => recurse("inf"), 2192 copy_from => copyfrom_sync($(from)); 2193 2194 reports: 2195 "DEBUG|DEBUG_$(this.bundle)":: 2196 "DEBUG $(this.bundle): copying directory $(from) to $(to)"; 2197} 2198 2199bundle agent file_copy(from, to) 2200# @brief Copy a file 2201# @param from source file 2202# @param to destination file 2203# 2204# **Example:** 2205# 2206# ```cf3 2207# methods: 2208# "" usebundle => file_copy("/tmp/z.txt", "/var/tmp/y.txt"); 2209# ``` 2210{ 2211 files: 2212 "$(to)" 2213 copy_from => copyfrom_sync($(from)); 2214 2215 reports: 2216 "DEBUG|DEBUG_$(this.bundle)":: 2217 "DEBUG $(this.bundle): copying file $(from) to $(to)"; 2218} 2219 2220body copy_from copyfrom_sync(f) 2221# @brief Copy a directory or file with digest checksums, preserving attributes and purging leftovers 2222# @param f the file or directory 2223{ 2224 source => "$(f)"; 2225 purge => "true"; 2226 preserve => "true"; 2227 type_check => "false"; 2228 compare => "digest"; 2229} 2230 2231bundle agent file_make(file, str) 2232# @brief Make a file from a string 2233# @param file target 2234# @param str the string data 2235# 2236# **Example:** 2237# 2238# ```cf3 2239# methods: 2240# "" usebundle => file_make("/tmp/z.txt", "Some text 2241# and some more text here"); 2242# ``` 2243{ 2244 vars: 2245 "len" int => string_length($(str)); 2246 summarize:: 2247 "summary" string => format("%s...%s", 2248 string_head($(str), 18), 2249 string_tail($(str), 18)); 2250 classes: 2251 "summarize" expression => isgreaterthan($(len), 40); 2252 2253 files: 2254 "$(file)" 2255 create => "true", 2256 edit_line => insert_lines($(str)), 2257 edit_defaults => empty; 2258 2259 reports: 2260 "DEBUG|DEBUG_$(this.bundle)":: 2261 "DEBUG $(this.bundle): creating $(file) with contents '$(str)'" 2262 if => "!summarize"; 2263 2264 "DEBUG $(this.bundle): creating $(file) with contents '$(summary)'" 2265 if => "summarize"; 2266} 2267 2268bundle agent file_make_mog(file, str, mode, owner, group) 2269# @brief Make a file from a string with mode, owner, group 2270# @param file target 2271# @param str the string data 2272# @param mode the file permissions in octal 2273# @param owner the file owner as a name or UID 2274# @param group the file group as a name or GID 2275# 2276# **Example:** 2277# 2278# ```cf3 2279# methods: 2280# "" usebundle => file_make_mog("/tmp/z.txt", "Some text 2281# and some more text here", "0644", "root", "root"); 2282# ``` 2283{ 2284 vars: 2285 "len" int => string_length($(str)); 2286 summarize:: 2287 "summary" string => format("%s...%s", 2288 string_head($(str), 18), 2289 string_tail($(str), 18)); 2290 classes: 2291 "summarize" expression => isgreaterthan($(len), 40); 2292 2293 files: 2294 "$(file)" 2295 create => "true", 2296 edit_line => insert_lines($(str)), 2297 perms => mog($(mode), $(owner), $(group)), 2298 edit_defaults => empty; 2299 2300 reports: 2301 "DEBUG|DEBUG_$(this.bundle)":: 2302 "DEBUG $(this.bundle): creating $(file) with contents '$(str)', mode '$(mode)', owner '$(owner)' and group '$(group)'" 2303 if => "!summarize"; 2304 2305 "DEBUG $(this.bundle): creating $(file) with contents '$(summary)', mode '$(mode)', owner '$(owner)' and group '$(group)'" 2306 if => "summarize"; 2307} 2308 2309bundle agent file_make_mustache(file, template, data) 2310# @brief Make a file from a mustache template 2311# @param file Target file to render 2312# @param template Path to mustache template 2313# @param data Data container to use 2314# 2315# **Example:** 2316# 2317# ```cf3 2318# vars: 2319# "state" data => datastate(); 2320# 2321# methods: 2322# "" usebundle => file_make_mustache( "/tmp/z.txt", "/tmp/z.mustache", @(state) ); 2323# ``` 2324{ 2325 files: 2326 "$(file)" 2327 create => "true", 2328 edit_template => "$(template)", 2329 template_method => "mustache", 2330 template_data => @(data); 2331 2332 reports: 2333 "DEBUG|DEBUG_$(this.bundle)":: 2334 "DEBUG $(this.bundle): rendering $(file) with template '$(template)'"; 2335} 2336 2337bundle agent file_make_mustache_with_perms(file, template, data, mode, owner, group) 2338# @brief Make a file from a mustache template 2339# @param file Target file to render 2340# @param template Path to mustache template 2341# @param data Data container to use 2342# @param mode File permissions 2343# @param owner Target file owner 2344# @param group Target file group 2345# 2346# **Example:** 2347# 2348# ```cf3 2349# vars: 2350# "state" data => datastate(); 2351# 2352# methods: 2353# "" usebundle => file_make_mustache( "/tmp/z.txt", "/tmp/z.mustache", @(state), 2354# 600, "root", "root" ); 2355# ``` 2356{ 2357 files: 2358 "$(file)" 2359 create => "true", 2360 edit_template => "$(template)", 2361 template_method => "mustache", 2362 perms => mog( $(mode), $(owner), $(group) ), 2363 template_data => @(data); 2364 2365 reports: 2366 "DEBUG|DEBUG_$(this.bundle)":: 2367 "DEBUG $(this.bundle): rendering $(file) with template '$(template)'"; 2368} 2369 2370bundle agent file_empty(file) 2371# @brief Make an empty file 2372# @param file target 2373# 2374# **Example:** 2375# 2376# ```cf3 2377# methods: 2378# "" usebundle => file_empty("/tmp/z.txt"); 2379# ``` 2380{ 2381 files: 2382 "$(file)" 2383 create => "true", 2384 edit_defaults => empty; 2385 2386 reports: 2387 "DEBUG|DEBUG_$(this.bundle)":: 2388 "DEBUG $(this.bundle): creating empty $(file) with 0 size"; 2389} 2390 2391bundle agent file_hardlink(target, link) 2392# @brief Make a hard link to a file 2393# @param target of link 2394# @param link the hard link's location 2395# 2396# **Example:** 2397# 2398# ```cf3 2399# methods: 2400# "" usebundle => file_hardlink("/tmp/z.txt", "/tmp/z.link"); 2401# ``` 2402{ 2403 files: 2404 "$(link)" 2405 move_obstructions => "true", 2406 link_from => linkfrom($(target), "hardlink"); 2407 2408 reports: 2409 "DEBUG|DEBUG_$(this.bundle)":: 2410 "DEBUG $(this.bundle): $(link) will be a hard link to $(target)"; 2411} 2412 2413bundle agent file_link(target, link) 2414# @brief Make a symlink to a file 2415# @param target of symlink 2416# @param link the symlink's location 2417# 2418# **Example:** 2419# 2420# ```cf3 2421# methods: 2422# "" usebundle => file_link("/tmp/z.txt", "/tmp/z.link"); 2423# ``` 2424{ 2425 files: 2426 "$(link)" 2427 move_obstructions => "true", 2428 link_from => linkfrom($(target), "symlink"); 2429 2430 reports: 2431 "DEBUG|DEBUG_$(this.bundle)":: 2432 "DEBUG $(this.bundle): $(link) will be a symlink to $(target)"; 2433} 2434