0

So i have been trying to fix a mistake i did in all the servers by using a playbook. Basicly i launched a playbook with logrotate to fix the growing logs problem, and in there is a log named btmp, which i wasnt supposed to rotate but did anyway by accident, and now logrotate changed its name to add a date to it and therefore braking the log. Now i want to use a playbook that will find a log named btmp in /var/log directory and rename it back, problem is that the file atm is different in each server for example 1 server has btmp-20210316 and the other has btmp-20210309, so in bash command line one would use wildcard "btmp*" to bypass thos problem, however this does not appear to work in playbook. So far i came up with this:

  tasks:
    - name: stat btmp*
      stat: path=/var/log
      register: btmp_stat

    - name: Move btmp
      command: mv /var/log/btmp* /var/log/btmp
      when: btmp_stat.stat.exists

However this results in error that the file was not found. So my question is how does one get the wildcard working in playbook or is there an equivalent way to find all files that have "btmp" in their names and rename them ? BTW all servers are Centos 7 servers.

So i will add my own solution aswell, even tho the answer solution is better. Make a bash script with a single line, anywhere in you ansible VM. Line is : mv /var/log/filename* /var/log/filename

And now create a playbook to operate this in target VM:

---
- hosts: '{{ server }}'
  remote_user: username
  become: yes
  become_method: sudo

  vars_prompt:
    - name: "server"
      prompt: "Enter server name or group"
      private: no

  tasks:
    - name: Move the script to target host VM
      copy: src=/anywhereyouwant/bashscript.sh dest=/tmp mode=0777

    - name: Execute the script
      command: sh /tmp/bashscript.sh

    - name: delete the script
      command: rm /tmp/bashscript.sh
Mairold Kunimägi
  • 125
  • 1
  • 1
  • 10
  • https://stackoverflow.com/a/34594089/2123530 – β.εηοιτ.βε Mar 16 '21 at 12:06
  • And from the module synopsis: *The command(s) will not be processed through the shell, so variables like `$HOSTNAME` and operations like `"*"`, `"<"`, `">"`, `"|"`, `";"` and `"&"` will not work. Use the [ansible.builtin.shell](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html#ansible-collections-ansible-builtin-shell-module) module if you need these features.* https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html#synopsis – β.εηοιτ.βε Mar 16 '21 at 12:08

1 Answers1

1

There's more than one way to do this in Ansible, and using the shell module is certainly a viable way to do it (but you would need the shell module in place of command as the latter does not support wildcards). I would solve the problem as follows:

  1. First create a task to find all matching files (i.e. /var/log/btmp*) and store them in a variable for later processing - this would look like this:
    - name: Find all files named /var/log/btmp*
      ansible.builtin.find:
        paths: /var/log
        patterns: 'btmp*'
      register: find_btmp

This task uses the find module to locate all files called btmp* in /var/log - the results are stored in a variable called find_btmp.

  1. Next create a task to copy the btmp* file to btmp. Now you may very well have more than 1 file pathing the above pattern, and logically you don't want to rename them all to btmp as this simply keeps overwriting the file every time. Instead, let's assume you want only the newest file that you matched - we can use a clever Jinja2 filter to get this entry from the results of the first task:
    - name: Copy the btmp* to the required filename
      ansible.builtin.copy:
        src: "{{ find_btmp.files | sort(attribute='mtime',reverse=true) | map(attribute='path') | first }}"
        dest: /var/log/btmp
        remote_src: yes
      when: find_btmp.failed == false

This task uses Ansible's copy module to copy our chosen source file to /var/log/btmp. The remote_src: yes parameter tells the copy module that the source file exists on the remote machine rather than the Ansible host itself.

We use a when clause to ensure that we don't run this copy operation if we failed to find any files.

Now let's break down that Jinja2 filter:

  • find_btmp.files - this is all of the files listed in our find_btmp variable
  • sort(attribute='mtime',reverse=true) - here we are sorting our list of files using the mtime (modification time) attribute - we're reverse sorting so that the newest entry is at the top of the list.
  • map(attribute='path') - we're using map to "extract" the path attribute of the files dictionary, as this is the only data we actually want to pass to the copy module - the path of the file itself
  • first - this selects only the first element in the list (i.e. the newest file as they were reverse sorted)
  1. Finally, you asked for a move operation - there's no native "move" module in Ansible so you will want to remove the source file after the copy - this can be done as follows (the Jinja2 filter is the same as before:
  - name: Delete the original file
    ansible.builtin.file:
      path: "{{ find_btmp.files | sort(attribute='mtime',reverse=true) | map(attribute='path') | first }}"
      state: absent
    when: find_btmp.failed == false

Again we use a when clause to ensure we don't delete anything if we didn't find it in the first place.

I have tested this on Ansible 3.1.0/ansible-base 2.10.7 - if you're running Ansible 2.9 or earlier, remove the ansible.builtin. from the module names (i.e. ansible.builtin.copy becomes copy.)

Hope this helps you out!

  • 1
    Glorious answer! Were nicely explained and works like charm. However i managed to fix the problem by making a 1 line shell script that only contained "mv /var/log/btmp* /var/log/btmp". Then i used a playbook to copy the script into target host, use it and then delete the script, very simple and worked also. Tho i will save your work as a playbook since its a safer way to do it! – Mairold Kunimägi Mar 17 '21 at 13:18
  • Thank you - I am a big fan of Ansible but I struggled in the early days especially around Jinja filters and dictionaries/lists - this is the kind of answer I wish I'd seen in those early days so glad to help out! – James Freeman Mar 18 '21 at 14:18