├── tests ├── .gitignore ├── roles │ └── yaegashi.blockinfile ├── expected │ ├── test-follow │ │ ├── link2.txt │ │ ├── real0.txt │ │ ├── real1.txt │ │ ├── link0.txt │ │ ├── link1.txt │ │ └── real2.txt │ ├── test-basic │ │ ├── remove.txt │ │ ├── insert.txt │ │ ├── update.txt │ │ ├── create.txt │ │ └── marker.txt │ ├── test-state │ │ ├── remove.txt │ │ ├── remove-absent.txt │ │ ├── remove-present.txt │ │ ├── insert.txt │ │ └── insert-present.txt │ ├── test-block │ │ ├── block.txt │ │ └── content.txt │ ├── test-insertab │ │ ├── insertafter.txt │ │ ├── insertbefore.txt │ │ ├── insertbof.txt │ │ ├── inserteof.txt │ │ ├── updateafter.txt │ │ ├── updatebefore.txt │ │ └── insertunmatched.txt │ └── test-replace │ │ └── index.html ├── fixtures │ ├── test-follow │ │ ├── link0.txt │ │ ├── link1.txt │ │ ├── link2.txt │ │ ├── real0.txt │ │ ├── real1.txt │ │ └── real2.txt │ ├── test-block │ │ ├── block.txt │ │ └── content.txt │ ├── test-basic │ │ ├── marker.txt │ │ ├── insert.txt │ │ ├── remove.txt │ │ └── update.txt │ ├── test-state │ │ ├── insert.txt │ │ ├── insert-present.txt │ │ ├── remove.txt │ │ ├── remove-absent.txt │ │ └── remove-present.txt │ ├── test-insertab │ │ ├── insertafter.txt │ │ ├── insertbof.txt │ │ ├── inserteof.txt │ │ ├── insertbefore.txt │ │ ├── insertunmatched.txt │ │ ├── updateafter.txt │ │ └── updatebefore.txt │ └── test-replace │ │ └── index.html ├── hosts ├── test-block.yml ├── test-replace.yml ├── test-follow.yml ├── test-state.yml ├── test-basic.yml ├── test-insertab.yml └── run.sh ├── meta └── main.yml ├── CONTRIBUTING.md ├── README.md └── library └── blockinfile.py /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /testing 2 | -------------------------------------------------------------------------------- /tests/roles/yaegashi.blockinfile: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /tests/expected/test-follow/link2.txt: -------------------------------------------------------------------------------- 1 | real2.txt -------------------------------------------------------------------------------- /tests/fixtures/test-follow/link0.txt: -------------------------------------------------------------------------------- 1 | real0.txt -------------------------------------------------------------------------------- /tests/fixtures/test-follow/link1.txt: -------------------------------------------------------------------------------- 1 | real1.txt -------------------------------------------------------------------------------- /tests/fixtures/test-follow/link2.txt: -------------------------------------------------------------------------------- 1 | real2.txt -------------------------------------------------------------------------------- /tests/fixtures/test-block/block.txt: -------------------------------------------------------------------------------- 1 | AAA BBB CCC 2 | -------------------------------------------------------------------------------- /tests/fixtures/test-block/content.txt: -------------------------------------------------------------------------------- 1 | AAA BBB CCC 2 | -------------------------------------------------------------------------------- /tests/hosts: -------------------------------------------------------------------------------- 1 | localhost ansible_connection=local 2 | -------------------------------------------------------------------------------- /tests/fixtures/test-basic/marker.txt: -------------------------------------------------------------------------------- 1 |

111 222 333

2 | -------------------------------------------------------------------------------- /tests/expected/test-basic/remove.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/expected/test-follow/real0.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/expected/test-follow/real1.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/expected/test-state/remove.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-basic/insert.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-follow/real0.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-follow/real1.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-follow/real2.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-state/insert.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/expected/test-state/remove-absent.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/expected/test-state/remove-present.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-state/insert-present.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/insertafter.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/insertbof.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/inserteof.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/insertbefore.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/insertunmatched.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | -------------------------------------------------------------------------------- /tests/fixtures/test-replace/index.html: -------------------------------------------------------------------------------- 1 | 111 222 333

!!! @@@ ###

2 | -------------------------------------------------------------------------------- /tests/expected/test-block/block.txt: -------------------------------------------------------------------------------- 1 | AAA BBB CCC 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | aaa bbb ccc 4 | # END ANSIBLE MANAGED BLOCK 5 | -------------------------------------------------------------------------------- /tests/expected/test-block/content.txt: -------------------------------------------------------------------------------- 1 | AAA BBB CCC 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | aaa bbb ccc 4 | # END ANSIBLE MANAGED BLOCK 5 | -------------------------------------------------------------------------------- /tests/expected/test-state/insert.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | # END ANSIBLE MANAGED BLOCK 6 | -------------------------------------------------------------------------------- /tests/expected/test-state/insert-present.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | # END ANSIBLE MANAGED BLOCK 6 | -------------------------------------------------------------------------------- /tests/expected/test-basic/insert.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | AAA BBB CCC 6 | # END ANSIBLE MANAGED BLOCK 7 | -------------------------------------------------------------------------------- /tests/expected/test-basic/update.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | aaa bbb ccc 4 | AAA BBB CCC 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/expected/test-follow/link0.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | AAA BBB CCC 6 | # END ANSIBLE MANAGED BLOCK 7 | -------------------------------------------------------------------------------- /tests/expected/test-follow/link1.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | AAA BBB CCC 6 | # END ANSIBLE MANAGED BLOCK 7 | -------------------------------------------------------------------------------- /tests/expected/test-follow/real2.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | AAA BBB CCC 6 | # END ANSIBLE MANAGED BLOCK 7 | -------------------------------------------------------------------------------- /tests/fixtures/test-basic/remove.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/fixtures/test-basic/update.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/fixtures/test-state/remove.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/fixtures/test-state/remove-absent.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/fixtures/test-state/remove-present.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | -------------------------------------------------------------------------------- /tests/expected/test-basic/create.txt: -------------------------------------------------------------------------------- 1 | # BEGIN ANSIBLE MANAGED BLOCK 2 | iface eth0 inet static 3 | address 192.168.0.1 4 | netmask 255.255.255.0 5 | # END ANSIBLE MANAGED BLOCK 6 | -------------------------------------------------------------------------------- /tests/expected/test-basic/marker.txt: -------------------------------------------------------------------------------- 1 |

111 222 333

2 | 3 |

aaa bbb ccc

4 |

AAA BBB CCC

5 | 6 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/insertafter.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | # BEGIN ANSIBLE MANAGED BLOCK 5 | aaa bbb ccc 6 | AAA BBB CCC 7 | # END ANSIBLE MANAGED BLOCK 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/insertbefore.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | # BEGIN ANSIBLE MANAGED BLOCK 4 | aaa bbb ccc 5 | AAA BBB CCC 6 | # END ANSIBLE MANAGED BLOCK 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/insertbof.txt: -------------------------------------------------------------------------------- 1 | # BEGIN ANSIBLE MANAGED BLOCK 2 | aaa bbb ccc 3 | AAA BBB CCC 4 | # END ANSIBLE MANAGED BLOCK 5 | 111 222 333 6 | !!! @@@ ### 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/inserteof.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | # BEGIN ANSIBLE MANAGED BLOCK 6 | aaa bbb ccc 7 | AAA BBB CCC 8 | # END ANSIBLE MANAGED BLOCK 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/updateafter.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | aaa bbb ccc 4 | AAA BBB CCC 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/updatebefore.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | aaa bbb ccc 4 | AAA BBB CCC 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/updateafter.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/fixtures/test-insertab/updatebefore.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | # BEGIN ANSIBLE MANAGED BLOCK 3 | xxx yyy zzz 4 | XXX YYY ZZZ 5 | # END ANSIBLE MANAGED BLOCK 6 | !!! @@@ ### 7 | 111 222 333 8 | !!! @@@ ### 9 | -------------------------------------------------------------------------------- /tests/expected/test-insertab/insertunmatched.txt: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | !!! @@@ ### 3 | 111 222 333 4 | !!! @@@ ### 5 | # BEGIN ANSIBLE MANAGED BLOCK 6 | aaa bbb ccc 7 | AAA BBB CCC 8 | # END ANSIBLE MANAGED BLOCK 9 | -------------------------------------------------------------------------------- /tests/expected/test-replace/index.html: -------------------------------------------------------------------------------- 1 | 111 222 333 2 | 3 |

aaa bbb ccc

4 |

AAA BBB CCC

5 | 6 |

!!! @@@ ###

7 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: YAEGASHI Takeshi 4 | description: Contains blockinfile module 5 | to insert/update/remove a text block surrounded by marker lines 6 | license: GPLv3+ 7 | min_ansible_version: 1.2 8 | categories: 9 | - system 10 | dependencies: [] 11 | -------------------------------------------------------------------------------- /tests/test-block.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - name: insert a block using block 8 | blockinfile: 9 | dest: "{{testing_dir}}/block.txt" 10 | backup: yes 11 | block: aaa bbb ccc 12 | - name: insert a block using block 13 | blockinfile: 14 | dest: "{{testing_dir}}/content.txt" 15 | backup: yes 16 | content: aaa bbb ccc 17 | -------------------------------------------------------------------------------- /tests/test-replace.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - name: insert a newline after 8 | replace: 9 | dest: "{{testing_dir}}/index.html" 10 | backup: yes 11 | regexp: (?i)()(?=.) 12 | replace: \1\n 13 | - name: insert a block after 14 | blockinfile: 15 | dest: "{{testing_dir}}/index.html" 16 | backup: yes 17 | marker: "" 18 | content: | 19 |

aaa bbb ccc

20 |

AAA BBB CCC

21 | insertafter: (?i) 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to blockinfile role/module 2 | 3 | If you're going to contribute and make a pull request in the GitHub project, 4 | please consider including some tests for features/fixes 5 | you want to add in the PR. 6 | 7 | # Module testing infrastructure 8 | 9 | Run `bash run.sh` in tests dir. 10 | It copies fixtures dir to testing (working) dir, 11 | then for each of tests (tests/*.yml) it runs ansible-playbook twice 12 | (the second run is for idempotency check) 13 | and see difference between files in testing dir and ones in expected dir. 14 | 15 | You can add your own tests by adding the following files and dirs: 16 | - tests/test-the-feature.yml - A playbook to test "the feature." 17 | - tests/fixtures/test-the-feature/... - Put fixtures to be tested. 18 | - tests/expected/test-the-feature/... - Put expected results after running playbook. 19 | 20 | -------------------------------------------------------------------------------- /tests/test-follow.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - shell: echo "Detected Ansible prior to 1.8" > {{testing_dir}}/SKIPPED 8 | && false 9 | when: ansible_version is not defined 10 | - name: update symlink file without follow 11 | blockinfile: | 12 | dest={{testing_dir}}/link0.txt backup=yes follow=no 13 | content="aaa bbb ccc\nAAA BBB CCC" 14 | - name: update symlink file with follow=no 15 | blockinfile: | 16 | dest={{testing_dir}}/link1.txt backup=yes follow=no 17 | content="aaa bbb ccc\nAAA BBB CCC" 18 | - name: update symlink file with follow=yes 19 | blockinfile: | 20 | dest={{testing_dir}}/link2.txt backup=yes follow=yes 21 | content="aaa bbb ccc\nAAA BBB CCC" 22 | -------------------------------------------------------------------------------- /tests/test-state.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - name: insert block without state 8 | blockinfile: 9 | dest: "{{testing_dir}}/insert.txt" 10 | backup: yes 11 | block: aaa bbb ccc 12 | - name: remove block without state 13 | blockinfile: 14 | dest: "{{testing_dir}}/remove.txt" 15 | backup: yes 16 | - name: insert block with state=present 17 | blockinfile: 18 | dest: "{{testing_dir}}/insert-present.txt" 19 | backup: yes 20 | block: aaa bbb ccc 21 | state: present 22 | - name: remove block with state=present 23 | blockinfile: 24 | dest: "{{testing_dir}}/remove-present.txt" 25 | backup: yes 26 | state: present 27 | - name: remove block with state=absent 28 | blockinfile: 29 | dest: "{{testing_dir}}/remove-absent.txt" 30 | backup: yes 31 | block: aaa bbb ccc 32 | state: absent 33 | -------------------------------------------------------------------------------- /tests/test-basic.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - name: insert block 8 | blockinfile: | 9 | dest={{testing_dir}}/insert.txt backup=yes 10 | content="aaa bbb ccc\nAAA BBB CCC" 11 | - name: update block 12 | blockinfile: | 13 | dest={{testing_dir}}/update.txt backup=yes 14 | content="aaa bbb ccc\nAAA BBB CCC" 15 | - name: remove block 16 | blockinfile: | 17 | dest={{testing_dir}}/remove.txt backup=yes 18 | content="" 19 | - name: alternative marker 20 | blockinfile: | 21 | dest={{testing_dir}}/marker.txt backup=yes 22 | marker="" 23 | content="

aaa bbb ccc

\n

AAA BBB CCC

" 24 | - name: create a file, with options in a hash 25 | blockinfile: 26 | dest: "{{testing_dir}}/create.txt" 27 | create: yes 28 | content: | 29 | iface eth0 inet static 30 | address 192.168.0.1 31 | netmask 255.255.255.0 32 | -------------------------------------------------------------------------------- /tests/test-insertab.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | vars: 3 | - testing_dir: "{{playbook_dir}}/testing" 4 | roles: 5 | - yaegashi.blockinfile 6 | tasks: 7 | - name: insert block after specific line 8 | blockinfile: | 9 | dest={{testing_dir}}/insertafter.txt backup=yes 10 | content="aaa bbb ccc\nAAA BBB CCC" insertafter=222 11 | - name: insert block before specific line 12 | blockinfile: | 13 | dest={{testing_dir}}/insertbefore.txt backup=yes 14 | content="aaa bbb ccc\nAAA BBB CCC" insertbefore=222 15 | - name: insert block at EOF 16 | blockinfile: | 17 | dest={{testing_dir}}/inserteof.txt backup=yes 18 | content="aaa bbb ccc\nAAA BBB CCC" insertafter=EOF 19 | - name: insert block at BOF 20 | blockinfile: | 21 | dest={{testing_dir}}/insertbof.txt backup=yes 22 | content="aaa bbb ccc\nAAA BBB CCC" insertbefore=BOF 23 | - name: insert block with unmatched regexp 24 | blockinfile: | 25 | dest={{testing_dir}}/insertunmatched.txt backup=yes 26 | content="aaa bbb ccc\nAAA BBB CCC" insertafter=999 27 | - name: update block with insertafter ignored 28 | blockinfile: | 29 | dest={{testing_dir}}/updateafter.txt backup=yes 30 | content="aaa bbb ccc\nAAA BBB CCC" insertafter=222 31 | - name: update block with insertbefore ignored 32 | blockinfile: | 33 | dest={{testing_dir}}/updatebefore.txt backup=yes 34 | content="aaa bbb ccc\nAAA BBB CCC" insertbefore=222 35 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd ${0%/*} 6 | 7 | rm -rf testing 8 | cp -R fixtures testing 9 | 10 | run() { 11 | p="$1" 12 | base="${1%.yml}" 13 | testing_dir="$PWD/testing/$base" 14 | expected_dir="$PWD/expected/$base" 15 | idlog="$PWD/testing/$base-id.log" 16 | skipped="$testing_dir/SKIPPED" 17 | echo "$p: Running ansible-playbook" 18 | if ! ansible-playbook -v -i hosts -e testing_dir=$testing_dir $p; then 19 | echo -n "$p: " 20 | if test -e $skipped; then 21 | cat $skipped 22 | exit 2 23 | else 24 | echo "ansible-playbook failed" 25 | exit 1 26 | fi 27 | fi 28 | echo "$p: Running ansible-playbook again" 29 | ansible-playbook -v -i hosts -e testing_dir=$testing_dir $p | tee $idlog 30 | if ! grep -q 'changed=0.*failed=0' $idlog; then 31 | echo "$p: Idempotency missing" 32 | exit 1 33 | fi 34 | echo "$p: Checking results" 35 | if ! diff -x "*~" -ruN $expected_dir $testing_dir; then 36 | echo "$p: Unexpected changes detected" 37 | exit 1 38 | fi 39 | } 40 | 41 | code=0 42 | 43 | tests=${@-*.yml} 44 | 45 | for i in $tests; do 46 | echo 47 | echo "$i: Starting..." 48 | if ( run $i ); then 49 | echo "$i: Passed" 50 | elif test $? = 2; then 51 | echo "$i: Skipped" 52 | else 53 | echo "$i: Failed" 54 | code=1 55 | fi 56 | done 57 | 58 | exit $code 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Role: blockinfile 2 | 3 | This role contains no tasks, but provides blockinfile module 4 | which might be useful when you want to maintain multi-line snippets 5 | in config files in /etc. 6 | 7 | Ansible Galaxy Page: [https://galaxy.ansible.com/list#/roles/1475](https://galaxy.ansible.com/list#/roles/1475) 8 | 9 | **Request for review:** 10 | [The pull request to ansible-modules-extras](https://github.com/ansible/ansible-modules-extras/pull/832) 11 | has been made to include blockinfile module 12 | in the official distribution of Ansible, 13 | which enables you to use blockinfile as a standard module without this role! 14 | 15 | If you use this module and feel it's useful, 16 | please leave some endorsement comments on the PR. 17 | I greatly appreciate if you're 18 | [an eligible reviewer (existing module author)](https://github.com/ansible/ansible-modules-extras/blob/devel/REVIEWERS.md) 19 | and could take some time to review the PR, 20 | otherwise if you could ask reviewers of your acquiaintance for the review. 21 | It needs two +1 votes from reviewers in order to be nominated for inclusion. 22 | 23 | ## blockinfile Module 24 | 25 | This module will insert/update/remove a block of multi-line text 26 | surrounded by the marker lines. 27 | 28 | Example task: 29 | 30 | ```yaml 31 | - blockinfile: 32 | dest: /etc/network/interfaces 33 | block: | 34 | iface eth0 inet static 35 | address 192.168.0.1 36 | netmask 255.255.255.0 37 | ``` 38 | 39 | Text inserted/updated by the task in /etc/network/interfaces: 40 | 41 | ``` 42 | # BEGIN ANSIBLE MANAGED BLOCK 43 | iface eth0 inet static 44 | address 192.168.0.1 45 | netmask 255.255.255.0 46 | # END ANSIBLE MANAGED BLOCK 47 | ``` 48 | 49 | It uses marker lines `# {BEGIN/END} ANSIBLE MANAGED BLOCK` as default. 50 | You can specify alternative marker lines by `marker` option 51 | when you need to update files in other formats like HTML, 52 | or run multiple blockinfile tasks on the same file. 53 | 54 | ### Options 55 | 56 | If this section doesn't show nicely in Ansible Galaxy Page, 57 | please refer to equivalent in 58 | [GitHub Page](https://github.com/yaegashi/ansible-role-blockinfile#options). 59 | 60 | > 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
parameterrequireddefaultchoicescomments
backup
nono
  • yes
  • no
Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly.
block
no
    The text to insert inside the marker lines. If it's missing or an empty string, the block will be removed as if state were specified to absent.

    80 |
    aliases: content
    create
    nono
    • yes
    • no
    Create a new file if it doesn't exist.
    dest
    yes
      The file to modify.

      93 |
      aliases: name, destfile
      follow
      (added in 1.8)
      nono
      • yes
      • no
      This flag indicates that filesystem links, if they exist, should be followed.
      group
      no
        name of the group that should own the file/directory, as would be fed to chown
        insertafter
        noEOF
        • EOF
        • *regex*
        If specified, the block will be inserted after the last match of specified regular expression. A special value is available; EOF for inserting the block at the end of the file. If specified regular expresion has no matches, EOF will be used instead.
        insertbefore
        no
        • BOF
        • *regex*
        If specified, the block will be inserted before the last match of specified regular expression. A special value is available; BOF for inserting the block at the beginning of the file. If specified regular expresion has no matches, the block will be inserted at the end of the file.
        marker
        no# {mark} ANSIBLE MANAGED BLOCK
          The marker line template. "{mark}" will be replaced with "BEGIN" or "END".
          mode
          no
            mode the file or directory should be. For those used to /usr/bin/chmod remember that modes are actually octal numbers (like 0644). Leaving off the leading zero will likely have unexpected results. As of version 1.8, the mode may be specified as a symbolic mode (for example, u+rwx or u=rw,g=r,o=r).
            owner
            no
              name of the user that should own the file/directory, as would be fed to chown
              selevel
              nos0
                level part of the SELinux file context. This is the MLS/MCS attribute, sometimes known as the range. _default feature works as for seuser.
                serole
                no
                  role part of SELinux file context, _default feature works as for seuser.
                  setype
                  no
                    type part of SELinux file context, _default feature works as for seuser.
                    seuser
                    no
                      user part of SELinux file context. Will default to system policy, if applicable. If set to _default, it will use the user portion of the policy if available
                      state
                      nopresent
                      • present
                      • absent
                      Whether the block should be there or not.
                      validate
                      noNone
                        The validation command to run before copying into place. The path to the file to validate is passed in via '%s' which must be present as in the example below. The command is passed securely so shell features like expansion and pipes won't work.
                        173 | 174 | ### Examples 175 | 176 | ```yaml 177 | - name: insert/update "Match User" configuation block in /etc/ssh/sshd_config 178 | blockinfile: 179 | dest: /etc/ssh/sshd_config 180 | block: | 181 | Match User ansible-agent 182 | PasswordAuthentication no 183 | ``` 184 | 185 | ```yaml 186 | - name: insert/update eth0 configuration stanza in /etc/network/interfaces 187 | (it might be better to copy files into /etc/network/interfaces.d/) 188 | blockinfile: 189 | dest: /etc/network/interfaces 190 | block: | 191 | iface eth0 inet static 192 | address 192.168.0.1 193 | netmask 255.255.255.0 194 | ``` 195 | 196 | ```yaml 197 | - name: insert/update HTML surrounded by custom markers after line 198 | blockinfile: 199 | dest: /var/www/html/index.html 200 | marker: "" 201 | insertafter: "" 202 | content: | 203 |

                        Welcome to {{ansible_hostname}}

                        204 |

                        Last updated on {{ansible_date_time.iso8601}}

                        205 | ``` 206 | 207 | ```yaml 208 | - name: remove HTML as well as surrounding markers 209 | blockinfile: 210 | dest: /var/www/html/index.html 211 | marker: "" 212 | content: "" 213 | ``` 214 | 215 | ## Requirements 216 | 217 | None. 218 | 219 | ## Role Variables 220 | 221 | None. 222 | 223 | ## Dependencies 224 | 225 | None. 226 | 227 | ## Example Playbook 228 | 229 | Complete playbook 230 | that makes SSH password authentication for specific user prohibited, 231 | then restarts sshd if needed. 232 | 233 | ```yaml 234 | --- 235 | - hosts: all 236 | remote_user: ansible-agent 237 | sudo: yes 238 | roles: 239 | - yaegashi.blockinfile 240 | tasks: 241 | - name: Prohibit SSH password authentication for $SUDO_USER 242 | blockinfile: 243 | dest: /etc/ssh/sshd_config 244 | backup: yes 245 | content: | 246 | Match User {{ansible_env.SUDO_USER}} 247 | PasswordAuthentication no 248 | notify: Restart sshd 249 | handlers: 250 | - name: Restart sshd 251 | service 252 | name: ssh 253 | state: restarted 254 | ``` 255 | 256 | ## License 257 | 258 | GPLv3+ 259 | 260 | ## Author Information 261 | 262 | [YAEGASHI Takeshi](https://github.com/yaegashi) 263 | -------------------------------------------------------------------------------- /library/blockinfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2014, 2015 YAEGASHI Takeshi 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | import re 22 | import os 23 | import tempfile 24 | 25 | DOCUMENTATION = """ 26 | --- 27 | module: blockinfile 28 | author: 29 | - 'YAEGASHI Takeshi (@yaegashi)' 30 | extends_documentation_fragment: 31 | - files 32 | - validate 33 | short_description: Insert/update/remove a text block 34 | surrounded by marker lines. 35 | version_added: '2.0' 36 | description: 37 | - This module will insert/update/remove a block of multi-line text 38 | surrounded by customizable marker lines. 39 | notes: 40 | - This module supports check mode. 41 | options: 42 | dest: 43 | aliases: [ name, destfile ] 44 | required: true 45 | description: 46 | - The file to modify. 47 | state: 48 | required: false 49 | choices: [ present, absent ] 50 | default: present 51 | description: 52 | - Whether the block should be there or not. 53 | marker: 54 | required: false 55 | default: '# {mark} ANSIBLE MANAGED BLOCK' 56 | description: 57 | - The marker line template. 58 | "{mark}" will be replaced with "BEGIN" or "END". 59 | block: 60 | aliases: [ content ] 61 | required: false 62 | default: '' 63 | description: 64 | - The text to insert inside the marker lines. 65 | If it's missing or an empty string, 66 | the block will be removed as if C(state) were specified to C(absent). 67 | insertafter: 68 | required: false 69 | default: EOF 70 | description: 71 | - If specified, the block will be inserted after the last match of 72 | specified regular expression. A special value is available; C(EOF) for 73 | inserting the block at the end of the file. If specified regular 74 | expresion has no matches, C(EOF) will be used instead. 75 | choices: [ 'EOF', '*regex*' ] 76 | insertbefore: 77 | required: false 78 | default: None 79 | description: 80 | - If specified, the block will be inserted before the last match of 81 | specified regular expression. A special value is available; C(BOF) for 82 | inserting the block at the beginning of the file. If specified regular 83 | expresion has no matches, the block will be inserted at the end of the 84 | file. 85 | choices: [ 'BOF', '*regex*' ] 86 | create: 87 | required: false 88 | default: 'no' 89 | choices: [ 'yes', 'no' ] 90 | description: 91 | - Create a new file if it doesn't exist. 92 | backup: 93 | required: false 94 | default: 'no' 95 | choices: [ 'yes', 'no' ] 96 | description: 97 | - Create a backup file including the timestamp information so you can 98 | get the original file back if you somehow clobbered it incorrectly. 99 | """ 100 | 101 | EXAMPLES = r""" 102 | - name: insert/update "Match User" configuation block in /etc/ssh/sshd_config 103 | blockinfile: 104 | dest: /etc/ssh/sshd_config 105 | block: | 106 | Match User ansible-agent 107 | PasswordAuthentication no 108 | 109 | - name: insert/update eth0 configuration stanza in /etc/network/interfaces 110 | (it might be better to copy files into /etc/network/interfaces.d/) 111 | blockinfile: 112 | dest: /etc/network/interfaces 113 | block: | 114 | iface eth0 inet static 115 | address 192.168.0.1 116 | netmask 255.255.255.0 117 | 118 | - name: insert/update HTML surrounded by custom markers after line 119 | blockinfile: 120 | dest: /var/www/html/index.html 121 | marker: "" 122 | insertafter: "" 123 | content: | 124 |

                        Welcome to {{ansible_hostname}}

                        125 |

                        Last updated on {{ansible_date_time.iso8601}}

                        126 | 127 | - name: remove HTML as well as surrounding markers 128 | blockinfile: 129 | dest: /var/www/html/index.html 130 | marker: "" 131 | content: "" 132 | """ 133 | 134 | 135 | def write_changes(module, contents, dest): 136 | 137 | tmpfd, tmpfile = tempfile.mkstemp() 138 | f = os.fdopen(tmpfd, 'wb') 139 | f.write(contents) 140 | f.close() 141 | 142 | validate = module.params.get('validate', None) 143 | valid = not validate 144 | if validate: 145 | if "%s" not in validate: 146 | module.fail_json(msg="validate must contain %%s: %s" % (validate)) 147 | (rc, out, err) = module.run_command(validate % tmpfile) 148 | valid = rc == 0 149 | if rc != 0: 150 | module.fail_json(msg='failed to validate: ' 151 | 'rc:%s error:%s' % (rc, err)) 152 | if valid: 153 | module.atomic_move(tmpfile, dest) 154 | 155 | 156 | def check_file_attrs(module, changed, message): 157 | 158 | file_args = module.load_file_common_arguments(module.params) 159 | if module.set_file_attributes_if_different(file_args, False): 160 | 161 | if changed: 162 | message += " and " 163 | changed = True 164 | message += "ownership, perms or SE linux context changed" 165 | 166 | return message, changed 167 | 168 | 169 | def main(): 170 | module = AnsibleModule( 171 | argument_spec=dict( 172 | dest=dict(required=True, aliases=['name', 'destfile']), 173 | state=dict(default='present', choices=['absent', 'present']), 174 | marker=dict(default='# {mark} ANSIBLE MANAGED BLOCK', type='str'), 175 | block=dict(default='', type='str', aliases=['content']), 176 | insertafter=dict(default=None), 177 | insertbefore=dict(default=None), 178 | create=dict(default=False, type='bool'), 179 | backup=dict(default=False, type='bool'), 180 | validate=dict(default=None, type='str'), 181 | ), 182 | mutually_exclusive=[['insertbefore', 'insertafter']], 183 | add_file_common_args=True, 184 | supports_check_mode=True 185 | ) 186 | 187 | params = module.params 188 | dest = os.path.expanduser(params['dest']) 189 | if module.boolean(params.get('follow', None)): 190 | dest = os.path.realpath(dest) 191 | 192 | if os.path.isdir(dest): 193 | module.fail_json(rc=256, 194 | msg='Destination %s is a directory !' % dest) 195 | 196 | if not os.path.exists(dest): 197 | if not module.boolean(params['create']): 198 | module.fail_json(rc=257, 199 | msg='Destination %s does not exist !' % dest) 200 | original = None 201 | lines = [] 202 | else: 203 | f = open(dest, 'rb') 204 | original = f.read() 205 | f.close() 206 | lines = original.splitlines() 207 | 208 | insertbefore = params['insertbefore'] 209 | insertafter = params['insertafter'] 210 | block = params['block'] 211 | marker = params['marker'] 212 | present = params['state'] == 'present' 213 | 214 | if insertbefore is None and insertafter is None: 215 | insertafter = 'EOF' 216 | 217 | if insertafter not in (None, 'EOF'): 218 | insertre = re.compile(insertafter) 219 | elif insertbefore not in (None, 'BOF'): 220 | insertre = re.compile(insertbefore) 221 | else: 222 | insertre = None 223 | 224 | marker0 = re.sub(r'{mark}', 'BEGIN', marker) 225 | marker1 = re.sub(r'{mark}', 'END', marker) 226 | if present and block: 227 | # Escape seqeuences like '\n' need to be handled in Ansible 1.x 228 | if ANSIBLE_VERSION.startswith('1.'): 229 | block = re.sub('', block, '') 230 | blocklines = [marker0] + block.splitlines() + [marker1] 231 | else: 232 | blocklines = [] 233 | 234 | n0 = n1 = None 235 | for i, line in enumerate(lines): 236 | if line.startswith(marker0): 237 | n0 = i 238 | if line.startswith(marker1): 239 | n1 = i 240 | 241 | if None in (n0, n1): 242 | n0 = None 243 | if insertre is not None: 244 | for i, line in enumerate(lines): 245 | if insertre.search(line): 246 | n0 = i 247 | if n0 is None: 248 | n0 = len(lines) 249 | elif insertafter is not None: 250 | n0 += 1 251 | elif insertbefore is not None: 252 | n0 = 0 # insertbefore=BOF 253 | else: 254 | n0 = len(lines) # insertafter=EOF 255 | elif n0 < n1: 256 | lines[n0:n1+1] = [] 257 | else: 258 | lines[n1:n0+1] = [] 259 | n0 = n1 260 | 261 | lines[n0:n0] = blocklines 262 | 263 | if lines: 264 | result = '\n'.join(lines)+'\n' 265 | else: 266 | result = '' 267 | if original == result: 268 | msg = '' 269 | changed = False 270 | elif original is None: 271 | msg = 'File created' 272 | changed = True 273 | elif not blocklines: 274 | msg = 'Block removed' 275 | changed = True 276 | else: 277 | msg = 'Block inserted' 278 | changed = True 279 | 280 | if changed and not module.check_mode: 281 | if module.boolean(params['backup']) and os.path.exists(dest): 282 | module.backup_local(dest) 283 | write_changes(module, result, dest) 284 | 285 | msg, changed = check_file_attrs(module, changed, msg) 286 | module.exit_json(changed=changed, msg=msg) 287 | 288 | # import module snippets 289 | from ansible.module_utils.basic import * 290 | from ansible.module_utils.splitter import * 291 | if __name__ == '__main__': 292 | main() 293 | --------------------------------------------------------------------------------