Showing posts with label ansible. Show all posts
Showing posts with label ansible. Show all posts

2019-04-04

Checking if filesystem supports d_type via Ansible

I had a task to add an assert to Ansible playbook to check that root / filesystem supports d_type (i.e. "directory entry type" and is important for Docker / Podman). Here is the result:

    - name: Read root filesystem type and device
      set_fact:
        root_fstype: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='fstype') | join(',') }}"
        root_device: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='device') | join(',') }}"
    - name: If root filesystem is xfs, get more info from it
      command:
        xfs_info "{{ root_device }}"
      register: xfs_info
      ignore_errors: true
      when: "root_fstype != 'ext4'"
    - name: Check that root filesystem supports directory entry type (aka d_type)
      assert:
        that:
          - "root_fstype == 'ext4' or ( root_fstype == 'xfs' and 'ftype=1' in xfs_info.stdout )"

First, we extract (more info on how we get one value from a list of dicts based on another value) root filesystem type (e.g. "ext4" or "xfs") and device (e.g. /dev/mapper/centos_something-root) from Ansible facts obtained by setup module (use ansible -u root -i inventory.ini -m setup all to see all the facts). Then we load additional info by xfs_info utility if the fs type is "xfs". And last step is finally to assert for d_type support: "ext4" is a clear win, when we got "xfs", "ftype=1" in xfs_info output is needed.

2017-06-03

Hard times with Ansible's to_datetime filter

I was a bit stupid. Took me some time to figure out how this is supposed to work, so here it is.

In Ansible 2.2 there is a new "to_datetime" filter (see bottom of that section) which transforms datetime string to datetime object.

Basic usage to convert string to datetime object, not that useful in this form:

$ ansible -c local -m debug -a "var=\"'2017-06-01 20:30:40'|to_datetime\"" localhost

localhost | SUCCESS => {
    "'2017-06-01 20:30:40'|to_datetime": "2017-01-06 20:30:40", 
    "changed": false
}

You can parse datetime string with arbitrary format (see python documentation for formatting options):

$ ansible -c local -m debug -a "var=\"'06/01/2017'|to_datetime('%m/%d/%Y')\"" localhost

localhost | SUCCESS => {
    "'06/01/2017'|to_datetime('%m/%d/%Y')": "2017-06-01 00:00:00", 
    "changed": false
}

In my case I wanted to parse start and end date of some registered task in ansible playbook (so in playbook string to parse would be registered_variable.start). Maybe you do not want datetime object, but UNIX timestamp from that (notice extra parenthesis):

$ ansible -c local -m debug -a "var=\"('2017-06-01 20:30:40.123456'|to_datetime('%Y-%m-%d %H:%M:%S.%f')).strftime('%s')\"" localhost

localhost | SUCCESS => {
    "('2017-06-01 20:30:40.123456'|to_datetime('%Y-%m-%d %H:%M:%S.%f')).strftime('%s')": "1496341840", 
    "changed": false
}

But actually I just wanted to know how much time given task took, so I can simply substract two datetime objects and then use .seconds of the resulting timedelta object:

$ ansible -c local -m debug -a "var=\"( '2017-06-01 20:30:40.123456'|to_datetime('%Y-%m-%d %H:%M:%S.%f') - '2017-06-01 20:29:35.234567'|to_datetime('%Y-%m-%d %H:%M:%S.%f') ).seconds\"" localhost

localhost | SUCCESS => {
    "( '2017-06-01 20:30:40.123456'|to_datetime('%Y-%m-%d %H:%M:%S.%f') - '2017-06-01 20:29:35.234567'|to_datetime('%Y-%m-%d %H:%M:%S.%f') ).seconds": "64", 
    "changed": false
}

In pre 2.2 version, you can use this inefficient call of local date command (you do not have to worry about that ugly '\\\"' escaping when in playbook):

$ ansible -c local -m debug -a "var=\"lookup('pipe', 'date -d \\\"2017-06-01 20:30:40.123456\\\" +%s')\"" localhost

localhost | SUCCESS => {
    "changed": false, 
    "lookup('pipe', 'date -d \"2017-06-01 20:30:40.123456\" +%s')": "1496341840"
}
Good luck!

2016-05-16

Serializing one task in an ansible playbook

In my workflow, I'm running playbook on all hosts from mine inventory, but in the middle I need to execute one command on a different system (lets creatively call it "central server") for all hosts in the inventory. And whats bad, that command is not capable to run in parallel, so I need to serialize it a bit. Initial version which does not do any serialization was:

- hosts: all
  remote_user: root
  tasks:
    - name: "Configure something on host"
      command: ...
    - name: "Configure something on central server for each host"
      command:
        some_command --host "{{ ansible_fqdn }}"
      delegate_to: centralserver.example.com
    - name: "Configure something else on host"
      command: ...

But "some_command" can not run multiple times in parallel and I can not fix it, so this is first way I have used to serialize it (so it runs only once on the central server at any time):

- hosts: all
  remote_user: root
  tasks:
    - name: "Configure something on host"
      command: ...
- hosts: all
  remote_user: root
  serial: 1
  tasks:
    - name: "Configure something on central server for each host"
      command:
        some_command --host "{{ ansible_fqdn }}"
      delegate_to: centralserver.example.com
- hosts: all
  remote_user: root
  tasks:
    - name: "Configure something else on host"
      command: ...

So I have created 3 plays from previous 1 in my playbook where the middle one is serialized by "serial: 1" option. I have not used "forks: 1", because you can set this value only in ansible.cfg or on ansible-playbook command line.

Another way was to keep only one play in a playbook, run given task only once and iterate over whole inventory:

- hosts: all
  remote_user: root
  tasks:
    - name: "Configure something on host"
      command: ...
    - name: "Configure something on central server for each host"
      command:
        some_command --host "{{ item }}"
      with_items: groups['all']
      run_once: true
      delegate_to: centralserver.example.com
    - name: "Configure something else on host"
      command: ...

In my case I needed hostname, so in the command I have used hostvariable {{ hostvars[item]['ansible_fqdn'] }}.

2016-05-04

Repeat command untill it passes in ansible playbook

Found numerous solutions, but they did not worked for me. Maybe it changed in Ansible 2.0 (I'm on ansible-2.0.1.0-2.el7). So here is what worked for me.
I needed to repeat package installation command until it passes (i.e. returns exit code 0; it was failing because of extreme conditions with memory allocation issues):
    - name: "Install katello-agent"
      action:
        yum
          name=katello-agent
          state=latest
      register: installed
      until: "{{ installed.rc }} == 0"
      retries: 10
      delay: 10
Note that although action: might look like something used only in old Ansible versions, it seems to be current way to do this do-until loops.