Search code examples
ansibleansible-inventory

Check if a file has certain strings


I have some files (file1), in some servers (group: myservers), which should look like this:

search www.mysebsite.com
nameserver 1.2.3.4
nameserver 1.2.3.5

This is an example of what this file should look like: The first line is mandatory ("search www.mysebsite.com"). The second and the third lines are mandatory as well, but the ips can change (although they should all be like this: ...).

I've being researching to implement some tasks using Ansible to check if the files are properly configured. I don't want to change any file, only check and output if the files are not ok or not. I know I can use ansible.builtin.lineinfile to check it, but I still haven't managed to find out how to achieve this. Can you help please?


Solution

  • For example, given the inventory

    shell> cat hosts
    [myservers]
    test_11
    test_13
    

    Create a dictionary of what you want to audit

      audit:
        files:
          /etc/resolv.conf:
            patterns:
              - '^search example.com$'
              - '^nameserver \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
          /etc/rc.conf:
            patterns:
              - '^sshd_enable="YES"$'
              - '^syslogd_flags="-ss"$'
    

    Note: The order of the patterns is mandatory. The tests will succeed if the first two lines of the files match the sequence of patterns. For example,

    shell> cat /etc/rc.conf
    sshd_enable="YES"
    syslogd_flags="-ss"
    
    shell> cat /etc/resolv.conf
    search example.com
    nameserver 10.1.0.1
    

    Declare the directory at the controller where the files will be stored

      my_dest: /tmp/ansible/myservers
    

    fetch the files

        - fetch:
            src: "{{ item.key }}"
            dest: "{{ my_dest }}"
          loop: "{{ audit.files|dict2items }}"
    

    Take a look at the fetched files

    shell> tree /tmp/ansible/myservers
    /tmp/ansible/myservers
    ├── test_11
    │   └── etc
    │       ├── rc.conf
    │       └── resolv.conf
    └── test_13
        └── etc
            ├── rc.conf
            └── resolv.conf
    
    4 directories, 4 files
    

    Audit the files. Create the dictionary host_files_results in the loop

        - set_fact:
            host_files_results: "{{ host_files_results|default({})|
                                    combine(host_file_dict|from_yaml) }}"
          loop: "{{ audit.files|dict2items }}"
          loop_control:
            label: "{{ item.key }}"
          vars:
            host_file_path: "{{ my_dest }}/{{ inventory_hostname }}/{{ item.key }}"
            host_file_lines: "{{ lookup('file', host_file_path).splitlines() }}"
            host_file_result: |
              [{% for pattern in item.value.patterns %}
              {{ host_file_lines[loop.index0] is regex pattern }},
              {% endfor %}]
            host_file_dict: "{ {{ item.key }}: {{ host_file_result|from_yaml is all }} }"
    

    gives

    ok: [test_11] => 
      host_files_results:
        /etc/rc.conf: true
        /etc/resolv.conf: true
    ok: [test_13] => 
      host_files_results:
        /etc/rc.conf: true
        /etc/resolv.conf: true
    

    Declare the dictionary audit_files that aggregates host_files_results

      audit_files: "{{ dict(ansible_play_hosts|
                            zip(ansible_play_hosts|
                                map('extract', hostvars, 'host_files_results'))) }}"
    

    gives

      audit_files:
        test_11:
          /etc/rc.conf: true
          /etc/resolv.conf: true
        test_13:
          /etc/rc.conf: true
          /etc/resolv.conf: true
    

    Evaluate the audit results

        - block:
            - debug:
                var: audit_files
            - assert:
                that: "{{ audit_files|json_query('*.*')|flatten is all }}"
                fail_msg: "[ERR] Audit of files failed. [TODO: list failed]"
                success_msg: "[OK]  Audit of files passed."
          run_once: true
    

    gives

     msg: '[OK]  Audit of files passed.'
    

    Example of a complete playbook for testing

    - hosts: myservers
    
      vars:
    
        my_dest: /tmp/ansible/myservers
    
        audit:
          files:
            /etc/resolv.conf:
              patterns:
                - '^search example.com$'
                - '^nameserver \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
            /etc/rc.conf:
              patterns:
                - '^sshd_enable="YES"$'
                - '^syslogd_flags="-ss"$'
    
        audit_files: "{{ dict(ansible_play_hosts|
                              zip(ansible_play_hosts|
                                  map('extract', hostvars, 'host_files_results'))) }}"
    
      tasks:
    
        - fetch:
            src: "{{ item.key }}"
            dest: "{{ my_dest }}"
          loop: "{{ audit.files|dict2items }}"
          loop_control:
            label: "{{ item.key }}"
    
        - set_fact:
            host_files_results: "{{ host_files_results|default({})|
                                    combine(host_file_dict|from_yaml) }}"
          loop: "{{ audit.files|dict2items }}"
          loop_control:
            label: "{{ item.key }}"
          vars:
            host_file_path: "{{ my_dest }}/{{ inventory_hostname }}/{{ item.key }}"
            host_file_lines: "{{ lookup('file', host_file_path).splitlines() }}"
            host_file_result: |
              [{% for pattern in item.value.patterns %}
              {{ host_file_lines[loop.index0] is regex pattern }},
              {% endfor %}]
            host_file_dict: "{ {{ item.key }}: {{ host_file_result|from_yaml is all }} }"
    
        - debug:
            var: host_files_results
    
        - block:
            - debug:
                var: audit_files
            - assert:
                that: "{{ audit_files|json_query('*.*')|flatten is all }}"
                fail_msg: "[ERR] Audit of files failed. [TODO: list failed]"
                success_msg: "[OK]  Audit of files passed."
          run_once: true