Search code examples
ansiblersyslog

Ansible: update rsyslog.conf multiline entry


I need a way to update one entry in rsyslog.conf on RHEL 8/9/10 systems, using Ansible (I'm using 2.16). Unfortunately, the new rsyslog format uses multiline entries that are not easily manipulated with something like lineinfile.

Original (this is from RHEL 10 beta; RHEL 8 and 9 are similar but not identical):

module(load="imjournal"             # provides access to the systemd journal
       StateFile="imjournal.state") # File to store the position in the journal

Desired:

module(load="imjournal"             # provides access to the systemd journal
       StateFile="imjournal.state"  # File to store the position in the journal
       Ratelimit.Interval="300"
       Ratelimit.Burst="30000")

It would be OK to lose the comments, or change the format of the module config, as long as the semantics are preserved. It is also acceptable to replace existing parameters with the factory defaults.

What I've looked at so far:

  • ansible.builtin.lineinfile. I can't see how it would work since it only operates on single lines.
  • ansible.builtin.replace: I'm not sure how to craft a regexp that would deal with the comments, and preserve existing parameters without duplicating the new ones on each run.
  • ansible.builtin.blockinfile: This seems to be my best bet, if I find a way to inject the markers in the right place.
  • Using lineinfile to inject the markers, then blockinfile to update the entry, using factory defaults. I just don't know how to put the end marker reliably in the right place.
  • Check if rsyslog has an API to update the configuration file. I didn't find anything (nor did I expect to).

Solution

  • As a hint. Given the file for testing

    shell> cat /tmp/rsyslog.conf
    module(load="imjournal"             # imjournal
           StateFile="imjournal.state"  # file
           )
    
    module(load="foo"                   # foo
           StateFile="foo.state"        # file
           )
    
    module(load="bar"                   # bar
           StateFile="bar.state"        # file
           )
    
    • Create markers. Declare the list
        mark_files:
          - path: /tmp/rsyslog.conf
            markers:
              - marker: imjournal
                regex1: 'module\(load="imjournal"(.*)'
                replace1: 'module(load="imjournal"'
                regex2: \)
                replace2: )
    

    and execute the block

        - name: Create markers
          when: markers | d(false) | bool
          block:
    
            - name: Create BEGIN markers
              replace:
                path: "{{ item.0.path }}"
                regexp: "{{ item.1.regex1 }}"
                replace: |-
                  {{ '#' }} BEGIN ANSIBLE MANAGED BLOCK {{ item.1.marker }}
                  {{ item.1.replace1 }}
              loop: "{{ mark_files | subelements('markers') }}"
    
            - name: Create END markers
              replace:
                path: "{{ item.0.path }}"
                regexp: '({{ item.1.regex1 }}[\s\S]*?){{ item.1.regex2 }}'
                replace: |-
                  \g<1>{{ item.1.replace2 }}
                  {{ '#' }} END ANSIBLE MANAGED BLOCK {{ item.1.marker }}
              loop: "{{ mark_files | subelements('markers') }}"
    

    This creates the markers

    shell> cat /tmp/rsyslog.conf
    # BEGIN ANSIBLE MANAGED BLOCK imjournal
    module(load="imjournal"
           StateFile="imjournal.state"  # file
           )
    # END ANSIBLE MANAGED BLOCK imjournal
    
    module(load="foo"                   # foo
           StateFile="foo.state"        # file
           )
    
    module(load="bar"                   # bar
           StateFile="bar.state"        # file
           )
    
    • Update the blocks. Declare the list
        update_files:
          - path: /tmp/rsyslog.conf
            markers:
              - marker: imjournal
                block: |
                  module(load="imjournal"
                         StateFile="imjournal.state"
                         Ratelimit.Interval="300"
                         Ratelimit.Burst="30000"
                         )
    

    and execute the task

        - name: Update files
          when: update | d(false) | bool
          blockinfile:
            path: "{{ item.0.path }}"
            marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.1.marker }}"
            block: "{{ item.1.block }}"
          loop: "{{ update_files | subelements('markers') }}"
    

    This updates the files

    shell> cat /tmp/rsyslog.conf
    # BEGIN ANSIBLE MANAGED BLOCK imjournal
    module(load="imjournal"
           StateFile="imjournal.state"
           Ratelimit.Interval="300"
           Ratelimit.Burst="30000"
           )
    # END ANSIBLE MANAGED BLOCK imjournal
    
    module(load="foo"                   # foo
           StateFile="foo.state"        # file
           )
    
    module(load="bar"                   # bar
           StateFile="bar.state"        # file
           )
    

    Notes:

    • The block that creates the markers is not idempotent. See example how to check the markers.

    • The regex2/replace2 attributes in the above example work with standalone closing parenthesis only. You'll have to modify this to your needs.

    • For more details see blockinfile markers.


    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        mark_files:
          - path: /tmp/rsyslog.conf
            markers:
              - marker: imjournal
                regex1: 'module\(load="imjournal"(.*)'
                replace1: 'module(load="imjournal"'
                regex2: \)
                replace2: )
    
        update_files:
          - path: /tmp/rsyslog.conf
            markers:
              - marker: imjournal
                block: |
                  module(load="imjournal"
                         StateFile="imjournal.state"
                         Ratelimit.Interval="300"
                         Ratelimit.Burst="30000"
                         )
    
      tasks:
    
        - name: Create markers
          when: markers | d(false) | bool
          block:
    
            - name: Create BEGIN markers
              replace:
                path: "{{ item.0.path }}"
                regexp: "{{ item.1.regex1 }}"
                replace: |-
                  {{ '#' }} BEGIN ANSIBLE MANAGED BLOCK {{ item.1.marker }}
                  {{ item.1.replace1 }}
              loop: "{{ mark_files | subelements('markers') }}"
    
            - name: Create END markers
              replace:
                path: "{{ item.0.path }}"
                regexp: '({{ item.1.regex1 }}[\s\S]*?){{ item.1.regex2 }}'
                replace: |-
                  \g<1>{{ item.1.replace2 }}
                  {{ '#' }} END ANSIBLE MANAGED BLOCK {{ item.1.marker }}
              loop: "{{ mark_files | subelements('markers') }}"
    
        - name: Update files
          when: update | d(false) | bool
          blockinfile:
            path: "{{ item.0.path }}"
            marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.1.marker }}"
            block: "{{ item.1.block }}"
          loop: "{{ update_files | subelements('markers') }}"