I am starting to look into using ansible for maintaining a set of servers. I have ran into a difficulty with lineinfile. I am setting up rsyslog and want to make sure a few lines are in the /etc/rsyslog.conf:
- name: set up rsyslog lineinfile: dest: /etc/rsyslog.conf line: '{{ item }}' with_items: - "*.* @@rsysserver.example.com:514" - "*.info;mail.none;authpriv.none;cron.none;user.none /var/log/messages" - "user.* -/var/log/user_messages"This works fine on a new box, but my problem is that some of the servers have been hand-maintained for a while and that means that the amount of whitespace in the lines may differ, so if there should be a line like user.* -/var/log/user_messages ansible will add on the last item in the rule above.
I could make three different rules like
lineinfile: dest: /etc/rsyslog.conf line: user.* -/var/log/user_messages regexp: 'user.\*\s+-/var/log/user_messages' state: presentshould work, but then I need to write three different rules to add the three lines to /etc/syslog.conf.
Is there any way I can set up one rule to maintain the file but use different regexes for each of the lines I want to add?
2 Answers
Systemic, more robust, and the idempotent way would be declaring the selector and action separately, and use it both in regexp and line. For example
- name: set up rsyslog lineinfile: dest: rsyslog.conf regexp: '^\s*{{ item.selector|regex_escape() }}\s+{{ item.action|regex_escape() }}\s*$' line: '{{ item.selector }} {{ item.action }}' loop: - selector: '*.*' action: '@@rsysserver.example.com:514' - selector: '*.info;mail.none;authpriv.none;cron.none;user.none' action: '/var/log/messages' - selector: 'user.*' action: '-/var/log/user_messages'gives
shell> cat rsyslog.conf
*.* @@rsysserver.example.com:514
*.info;mail.none;authpriv.none;cron.none;user.none /var/log/messages
user.* -/var/log/user_messagesUse backrefs if you want to keep the spacing between the selector and action. For example, the task below
- name: set up rsyslog lineinfile: dest: rsyslog.conf backrefs: true regexp: '^\s*{{ item.selector|regex_escape() }} (\s*){{ item.action|regex_escape() }}\s*$' line: '{{ item.selector }} \1{{ item.action }}' loop: - selector: '*.*' action: '@@rsysserver.example.com:514' - selector: '*.info;mail.none;authpriv.none;cron.none;user.none' action: '/var/log/messages' - selector: 'user.*' action: '-/var/log/user_messages'will keep this file unchanged
shell> cat rsyslog.conf
*.* @@rsysserver.example.com:514
*.info;mail.none;authpriv.none;cron.none;user.none /var/log/messages
user.* -/var/log/user_messagesUnfortunately, new lines won't be added. Quoting from backrefs
if the regexp does not match anywhere in the file, the file will be left unchanged
See format to try more sophisticated formatting.
1Write a rule or script that removes the lines first, then use your "set up rsyslog" task to add it back in. Once all the files are fixed and consistent you can remove the rule or script that was removing the line.
Alternatively, have you considered managing the entire /etc/rsyslog.conf file with Ansible instead of trying to add/remove/update individual lines from the file? This is how I would do it if possible.