Ansible snippets

# login as user ubuntu, use python3 and relogin as root
- hosts:
    - vm1
    - vm2
  become: yes
  vars:
    ansible_python_interpreter: /usr/bin/python3
    ansible_ssh_user: ubuntu
  tasks:
    - include: "{{ inventory_hostname }}.yml"
 
# show host
- debug:
    msg="{{ ansible_user_id }}"
 
# show host
- debug:
    msg="{{ inventory_hostname }}"
 
# show host groups
- debug:
    msg: "{{ group_names }}"
 
- debug:
    var: hostvars[inventory_hostname]
 
- debug:
    msg: "{{ ansible_system_vendor }} / {{ ansible_product_name }}"
 
- debug:
    msg: "ansible_default_ipv4["address"]"
  when: ansible_default_ipv4.gateway == "1.2.3..4"
 
- debug:
    msg: "{{ vms | length }}"
 
- debug:
    msg="{{ playbook_dir }} / {{ inventory_dir }} / {{ role_path }}"
 
- name: Dictonary to list
  debug:
    msg="{{ my_packages | join(' ') }}"
 
- name: set default value
  debug:
    msq: "{{ my_variable | default('bar') }}"
 
- set_facts:
    ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
 
  vars:
    os_cloud: "{{ lookup('env','OS_CLOUD') | default('dev-foo', true) }}"
    os_user: "{{ os_cloud.split('-')[1] | default(lookup('env','OS_USERNAME'), true) | default(lookup('env','USER'), true)}}"
 
# check if directory exists
- block:
  - name: Remove default configuration
    file:
      state: absent
      path: /etc/icinga/objects
    when: check_path.stat.exists == false
 
  - name: Deploy configuration
    git:
      repo: git@git.example.com:foo/icinga.git
      dest: /etc/icinga/objects
      accept_hostkey: yes
    notify: icinga restart
 
  when: check_path.stat.exists == false
 
- name: Directory exists already
  debug:
    msg: "Do something else..."
  when: check_path.stat.exists
 
- name: get env variable
  debug:
    msg:  "{{ lookup('env','HOME') }}"
 
- name: Copy directory on remote when not already exists
  command: cp -a /tmp/foo /tmp/bar
  args:
    creates: /tmp/bar
 
# check if package installed
- name: Check if package is already installed
  command: dpkg-query -W package_name
  register: dpkg_query
  changed_when: false
  failed_when: dpkg_query.rc > 1
- name: Install package
  package:
    name: package_name
  when: dpkg_query.rc > 0
 
# if / else condition
- set_fact:
    second_var: "{{ 'foo' if first_var != 'stable' else 'bar' }}" 
 
- set_fact:
    second_var: "{{ ( first_var != 'stable' ) | ternary('foo','bar') }}" 
 
- name: Configure locale
  locale_gen:
    name: "{{ item }}"
  with_items:
    - de_DE.UTF-8
    - en_US.UTF-8
 
# get distribution
- setup:
    filter: ansible_distribution
- debug:
    msg: "{{ ansible_distribution }}"
 
- setup:
- debug:
    msg: "{{ ansible_facts | to_nice_json }}"
 
- name: Configure timezone
# todo: (require dbus package)
#  timezone:
#    name: "Europe/Berlin"
#    hwclock: local
  copy:
    content: "Europe/Berlin"
    dest: /etc/timezone
  tags: timezone
 
- name: Add directory to PATH variable
  lineinfile:
    dest: "/home/foo/.profile"
    backrefs: yes
    regexp: 'PATH=(["]*)((?!.*?/home/foo/bin).*?)(["]*)$'
    line: 'PATH=\1\2:/home/foo/bin\3'
 
- name: Generate SSH key for a user with customized comment
  user:
    name: nagios
    generate_ssh_key: yes
    ssh_key_comment: nagios@{{ inventory_hostname }}
 
- name: check current timezone
  shell: cat /etc/timezone
  register: get_timezone
- name: set /etc/timezone
  shell: echo "{{ timezone }}" > /etc/timezone
  when: '"{{ timezone }}" not in get_timezone.stdout'
  notify: update tzdata
 
- name: Deploy SSH key to inventory groups
  authorized_key:
    user: root
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
  delegate_to: "{{ item }}"
  with_items: "{{  }}"
  with_items:
    - "{{ groups.webserver }}"
    - "{{ groups['proxy-server'] }}"
  ignore_errors: yes
 
#  when: inventory_hostname in groups['www']
 
- name: Get parameter from ini file from localhost
  debug:
    msg: "User in production  is {{ lookup('ini', 'user section=production  file=users.ini') }}"
 
- name: Remove /foo/bar if its a directory
  stat:
    path: /foo/bar
  register: path
- file:
    path: /foo/bar
    state: absent
  when: path.stat.isdir is defined and path.stat.isdir
 
 
- name: Configure network
  copy:
    content: |
      auto lo
      iface lo inet loopback
 
      auto eth0
      iface eth0 inet static
      address {{ ansible_default_ipv4.address }}
      netmask 255.255.255.0
      gateway 10.0.3.254
    dest: /etc/network/interfaces
 
- name: Configure DNS
  copy:
    content: |
      nameserver 10.0.3.1
      domain example.com
      search example.com
    dest: /etc/resolv.conf
 
- name: get public IP address from ipify.org
  ipify_facts:
- debug:
    msg: "{{ ipify_public_ip }}"
 
# list VMs
- name: List VMs
  virt:
    command: list_vms
  register: vms
- debug:
    msg: "{{ item }}"
  with_items: "{{ vms.list_vms }}"
 
- debug:
    msg: "{{ hostvars['www.example.com']['ansible_ssh_host_key_ecdsa_public'] }}"
 
- include_tasks: prerequisites_{{ ansible_os_family | lower }}.yml
 
# Create LV
- stat:
    path: /dev/vg1/images
  register: p
- name: Create LV /dev/vg1/images
  lvol:
    vg: vg1
    lv: images
#    size: "100%FREE"
    size: 430G
  when: not p.stat.exists
 
# Create filesystem
- name: Format /dev/vg1/images
  filesystem:
    fstype: ext4
    dev: /dev/vg1/images
 
# Create mountpoint for filesystem
- name: Configure /etc/fstab
  mount:
    path: /var/lib/libvirt/images
    src: /dev/vg1/images
    fstype: ext4
    state: mounted
 
# ipaddr filter
- hosts: localhost
  vars:
    my_ip: "{{ ansible_default_ipv4.network }}/{{ ansible_default_ipv4.netmask }}"
  tasks:
    - debug: msg="network {{ my_ip | ipaddr('network') }}"
    - debug: msg="netmask {{ my_ip | ipaddr('netmask') }}"
 
# get host ip addresses
    - debug: var=ansible_all_ipv4_addresses
    - debug: var=ansible_default_ipv4.address
 
- debug: msg="{{ ansible_virtualization_type }}"
# lxc, kvm, VMware
 
- debug: msg="{{ ansible_virtualization_role }}"
# host, guest
 
# override systemd service
- name: Create /etc/systemd/system/apache2.service.d directory
  file:
    path: /etc/systemd/system/apache2.service.d
    state: directory
    owner: root
    group: root
    mode: 0755
 
- name: Override apache2.service
  ini_file:
    dest: /etc/systemd/system/apache2.service.d/override.conf
    section: "{{ item.section }}"
    option: "{{ item.option }}"
    value: "{{ item.value }}"
    no_extra_spaces: yes
  with_items:
    - { section: Service, option: PIDFile, value: "/var/run/apache2/apache2.pid" }
    - { section: Service, option: Restart, value: "on-failure" }
    - { section: Service, option: RestartSec, value: "30s" }
  notify:
    - systemctl daemon-reload
 
- name: Check if the disk is partitioned and also ignore sda
  stat: path=/dev/{{item}}1
  with_items: disk_var
  when: item != 'sda'
  register: device_stat
- name: Create GPT partition table
  command: /sbin/parted -s /dev/{{ item.item }} mklabel gpt
  with_items: device_stat.results
  when:
    - not item | skipped
    - item.stat.exists == false
 
- name: Remove all cronjobs
  shell: crontab -r
  become: true
  become_user: "{{ default_user }}"
  ignore_errors: yes
 
- set_fact:
    dl_url: http://git.example.com/script.sql
- name: get sql
  uri:
    method: GET
    headers:
      PRIVATE-TOKEN: "your_private_token"
    url: "{{ dl_url }}"
    dest: /tmp/bar.sql
  environment:
    http_proxy: http://router.example.com:3128
 
- name: Check if the disk is partitioned and also ignore sda
  stat: path=/dev/{{item}}1
  with_items: disks
  when: item != "sda"
  register: device_stat
- name: Create GPT partition table
  command: /sbin/parted -s /dev/{{ item.item }} mklabel gpt
  with_items: device_stat.results
  when:
    - not item | skipped
    - item.stat.exists == false
 
- name: Create rsnapshot daily backup
  cron:
    name: rsnapshot daily backup
    hour: 1
    minute: 25
    job: /usr/bin/rsnapshot daily
 
- name: Create rsnapshot weekly backup
  cron:
    name: rsnapshot weekly backup
    hour: 2
    minute: 25
    weekday: 1
    job: /usr/bin/rsnapshot weekly
 
 - name: Create rsnapshot monthly backup
  cron:
    name: rsnapshot monthly backup
    hour: 3
    minute: 25
    day: 1
    job: /usr/bin/rsnapshot monthly
 
# dconf (read values with "dconf watch /" command)
# https://docs.ansible.com/ansible/2.4/dconf_module.html
- name: Read currently available keyboard layouts in Gnome
  dconf:
    key: /org/gnome/desktop/input-sources/sources
    state: read
  register: dconf
  become: yes
  become_user: "{{ default_user }}"
 
- debug:
    msg: "{{ dconf }}"
 
- name: Configure Gnome
  dconf:
    key: "{{ item.key }}"
    value: "{{ item.value }}"
  with_items:
    - { key: "/com/canonical/indicator/datetime/show-date", value: "true" }
  become: yes
  become_user: "{{ default_user }}"
 
# all nodes except
- name: show all the hosts matching the pattern, ie all but the group www
  debug:
    msg: "{{ item }}"
  with_inventory_hostnames:
    - all:!www
 
# format json output
- debug:
    msg: "{{ hostvars[inventory_hostname] | to_nice_json }}"
 
 
- name: Force reboot on HP Gen8 nodes
  shell: "{{ item }}"
  async: 1
  poll: 0
  with_items:
    - sync
    - sleep 2 && echo 1 > /proc/sys/kernel/sysrq
    - sleep 2 && echo b > /proc/sysrq-trigger
  when: ansible_product_name == "ProLiant DL380p Gen8"
 
- name: Print item index
  debug:
    msg: "{{ item }} with index {{ idx + 1 }}"
  loop:
    - a
    - b
    - c
  loop_control:
    index_var: idx
 
- name: Prepare Nginx VHosts
  copy:
    content: |
      server {
          listen 80;
          listen [::]:80 ;
          server_name {% if idx == 0 %}_{% else %}{{ item }}{% endif %};
 
          root /var/www/{{ item }}/html;
          index index.html;
 
          location / {
              try_files $uri $uri/ =404;
          }
      }
    dest: /etc/nginx/sites-available/{{ item }}
    owner: root
    group: root
    mode: 0644
  loop: "{{ vhosts }}"
  loop_control:
    index_var: idx
 
# list to file (template)
- name: Deploy HAProxy network_allowed.cfg configuration
  copy:
    dest: /etc/haproxy/network_allowed.cfg
    content: |
      {% for item in acl_network_allowed %}
      {{ item }}
      {% endfor %}
    mode: 0644
    owner: root
    group: root
  notify: restart haproxy
 
# test-me:
{{ ansible_env.HOME }}
 
"ansible_distribution_release": "bionic", 
"ansible_distribution_major_version": "18", 
"ansible_distribution_version": "18.04", 
 
  when: ansible_distribution_major_version|int < 20
 
addresses: [{{ ansible_default_ipv4.address }}/24]
#gateway4: {{ ansible_default_ipv4.address.split('.')[0:3] | join('.') }}.1
gateway4: {{ ansible_default_ipv4.gateway }}
 
# dns
# sudo apt install -y python-dnspython
- debug:
      msg: "{{ lookup('dig','www.example.com' ) }}"
 
# nginx
- name: Configure nginx sites
  copy:
    content: |
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
          server_name _;
 
          root /var/www/html;
          index index.html;
 
          location / {
              try_files $uri $uri/ =404;
              autoindex on;
          }
 
          location ~ /\.git {
            deny all;
          }
      }
    dest: /etc/nginx/sites-available/default
    owner: root
    group: root
    mode: 0644
  notify: service nginx restart
 
# calculate ip
- hosts: localhost
  vars:
    mgmt_net: 10
    ipmi_net: 20
    ip: "{{ lookup('dig', 'www.example.com').split('.') }}"
  tasks:
    - debug:
        msg: "{{ ip[0] }}.{{ ipmi_net }}.{{ ip[2] }}.{{ ip[3] }}"
    - debug:
        msg: "{{ lookup('dig', 'www.example.com') | replace('.{{ mgmt_net }}.', '.{{ ipmi_net }}.') }}"
 
- name: Upload file into s3 object storage
  os_object:
    cloud: "{{ os_cloud }}"
    name: fstab
    container: config
    filename: /etc/fstab
 
- name: Install a deb package dirctly from the internet
  apt:
    deb: https://example.com/python-ppq_0.1-1_all.deb
 
- name: Update APT cache and remove useless packages / dependencies
  apt:
    update_cache: yes
    upgrade: yes
    autoclean: yes
    autoremove: yes
 
- name: when inventory_hostname in dictonary
  loop: "{{ www }}"
  when: "inventory_hostname == item"
 
- debug:
    msg: |
      os_cloud: {{ os_cloud }}
      os_user: {{ os_user }}
 
- name: Debconf example
  debconf:
    name: "{{ debconf.name }}"
    question: "{{ debconf.question }}"
    value: "{{ debconf.value }}"
    vtype: "{{ debconf.vtype }}"
  loop:
    - { name: "percona-xtradb-cluster-server-5.7", question: "percona-xtradb-cluster-server-5.7/root-pass", value: "{{ database_password }}", vtype: "password" }
    - { name: "percona-xtradb-cluster-server-5.7", question: "percona-xtradb-cluster-server-5.7/re-root-pass", value: "{{ database_password }}", vtype: "password" }
  loop_control:
    loop_var: debconf
  no_log: true
  changed_when: false
 
- name: Install hardware relevant packages
  package:
    name: "{{ item }}"
  loop:
    - lm-sensors
    - hddtemp
  when: ansible_virtualization_role == "host"
 
- name: Mark package as hold in APT
  dpkg_selections:
    name: "{{ item }}"
    selection: hold
  loop:
    - docker-ce
    - linux-image-generic
 
- name: Ensure files exists
  file:
    path: /etc/file.conf
    mode: 0644
    owner: root
    group: root
    state: touch
 
# when group names contains strings
when: group_names | select('foo','bar') | list | count > 0
 
# or condition with multiple lines
when: >
  var1 != "success" or
  var2 != "foo"
 
# parse json
when: (output_text.stdout | from_json).is_master
 
- name: Check HW RAID
  shell: storcli /c0/v0 show J
  register: output
 
- set_fact:
    hwraid_output: "{{ output.stdout | from_json }}"
 
- debug:
    msg: "{{ hwraid_output.Controllers[0]['Response Data']['Virtual Drives'][0]['State'] }}"
 
- name: Disable lvmetad
  command: "sed -i 's/use_lvmetad = 1/use_lvmetad = 0/g' {{ tmp_rootfs_mount }}/etc/lvm/lvm.conf"
  replace:
    path: "{{ tmp_rootfs_mount }}/etc/lvm/lvm.conf"
    regexp: 'use_lvmetad = (.*)$'
    replace: 'use_lvmetad = 0'
 
# check whather file exists
- debug:
    msg: "host file already exists"
  when: etchosts is exists
  vars:
    etchosts: "/etc/hosts"
 
- name: generate dh params
command: sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 
args: 
  creates: /etc/ssl/certs/dhparam.pem
 
- name: upgrade software
command: /opt/software/bin/upgrade 
args:
  removes: etc/software/software.conf.upgrade

Docker

- name: Ensure some containers are stopped and dont autostart on boot
  docker_container:
    name: "{{ item }}"
    restart_policy: no
    state: stopped
  loop:
    - container1
    - container2
 
- name: Gather container list
  command: "docker ps --filter status=running --format {{ '{{.Names}}' }}"
  register: containers
 
- name: Enable autostart for running containers
  shell: docker update --restart=always $(docker ps -q)

Find disk by model

- name: Find disk by model
  find:
    paths: /dev/disk/by-id
    patterns: "*SSDPEL1K375GA*"
    excludes: "*part*"
    file_type: link
  register: find_result
 
- debug:
    msg: "{{ (find_result.files | first).path }}"
 
- debug:
    msg: "{{ item.path }}"
  with_items: "{{ find_result.files }}"
 
- stat:
    path: "{{ (find_result.files | first).path }}"
  register: stat_result
- debug:
    msg: "{{ stat_result.stat.lnk_source }}"
 
- name: Get current nginx container version
  shell: docker inspect --format=\{\{' .Config.Image '\}\} nginx | cut -d':' -f2
  register: result
  changed_when: false

Template

- include_vars:
    file: customers.yml
    name: customers
 
- blockinfile:
    dest: stunnel.conf
    block: "{{ lookup('template', 'stunnel.j2') }}"
    marker: "; {mark} ANSIBLE MANAGED BLOCK FOR {{ cust }}"

Variables

  # variable contains variable
  vars:
    os_env: dev
    user_name_dev: user1dev
    user_name: "{{ vars['user_name_' + os_env] }}"
 
- debug:
    msg: "{{ ansible_facts.cmdline.BOOTIF }}"
 
- debug:
    msg: "{{ ansible_default_ipv4.macaddress }}"
 
- name: Print some debug information 
    vars: 
    msg: |
        Module Variables ("vars"):
        --------------------------------
        {{ vars | to_nice_json }} 
 
        Environment Variables ("environment"):
        --------------------------------
        {{ environment | to_nice_json }} 
 
        GROUP NAMES Variables ("group_names"):
        --------------------------------
        {{ group_names | to_nice_json }}
 
        GROUPS Variables ("groups"):
        --------------------------------
        {{ groups | to_nice_json }}
 
        HOST Variables ("hostvars"):
        --------------------------------
        {{ hostvars | to_nice_json }} 
 
    debug: 
    msg: "{{ msg.split('\n') }}"       
    tags: debug_info
 
- setup:
- name: Debug vars to /tmp/vars.json
  copy:
    content: "{{ vars | to_nice_json }}"
    dest: /tmp/vars.json
 
- name: Display all variables/facts known for a host
  debug:
    var: hostvars[inventory_hostname]
  tags: debug_info

Include role inside of task

- include_role:
    apply:
      become: true  
    name: "{{ role_item }}"
  loop_control:
    loop_var: role_item
  loop:
    - my-role1

APT

- name: Install a .deb package from the internet.
  apt:
    deb: https://example.com/python-ppq_0.1-1_all.deb

Replace string
https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html

# remove digits
os_type: "{{ inventory_hostname.split('-')[1] | regex_replace('[0-9]', '') }}"

Cofigure snapd proxy

- hosts: localhost
  tasks:
    - name: Configure snap proxy
      become: yes
      ini_file:
        path: /etc/systemd/system/snapd.service.d/http-proxy.conf
        section: Service
        option: Environment
        value: '"http_proxy=http://proxy.example.com:8080"'
        mode: 0644
        create: yes
      notify: restart snapd
 
  handlers:
    - name: restart snapd
      systemd:
        name: snapd
        state: restarted
        daemon_reload: yes
- name: Find storage disks by model
  find:
    paths: /dev/disk/by-id
    patterns: "*SSDPE2KX080T8*"
    excludes: "*part*"
    file_type: link
  register: find_result
 
- set_fact:
    storage_disks_path: []
 
- name: Get storage disk device path
  set_fact:
    storage_disks_path: "{{ storage_disks_path }} + [ '{{ item.path }}' ]"
  with_items: "{{ find_result.files }}"
 
 
- debug:
    msg: "{{ item.0 }}_{{ item.1 }}"
  with_together:
    - "{{ range(1, (storage_disks_path | length) + 1) | list + range(1, (storage_disks_path | length) + 1) | list }}"
    - "{{ range(1, (storage_disks_path | length) * 2 + 1, 2) | list + range(2, (storage_disks_path | length) * 2 + 2, 2) | list }}"
 
 
- debug:
    msg: "{{ item.0 }}_{{ item.1 }}"
  with_together:
    - "{{ storage_disks_path }}"
    - "{{ range(1, (((storage_disks_path | length)) + 1) * 2, 2) | list }}"
 
- debug:
    msg: "{{ item.0 }}_{{ item.1 }}"
  with_together:
    - "{{ storage_disks_path }}"
    - "{{ range(2, (((storage_disks_path | length)) + 1) * 2, 2) | list }}"
 
 
- debug:
    msg: "{{ item }}"
  with_sequence: start=0 end="{{ (storage_disks.files | length) * 2}}"
 
 
- debug:
    msg: "{{ item.1.path }}-{{ item.0 }}"
  with_nested:
    - [ 1, 2 ]
    - "{{ storage_disks.files | length }}"
 
 
- debug:
    msg: "{{ item.path }}_{{ idx + 1}}"
  loop:
    "{{ storage_disks_result.files + storage_disks_result.files }}"
  loop_control:
    index_var: idx
 
 
- set_fact:
    - base_list: [1, 2, 3, 4]
    - exclude_list: [2, 4]
    - add_list: [5, 6]
 
  tasks:
    - debug:
        msg: "{{ base_list | union(add_list) | difference(exclude_list) }}"
 
 
- set_fact:
    device:
      - sdb: 2
      - sdc: 3
      - sdd: 4
 
- name: Debug device var
  debug:
    msg: "{% for key, value in item.iteritems() %}{% for i in range(value) %} {{ key }} {{ loop.index }} {% endfor %}{% endfor %}"
  loop: "{{ device }}"
 
 
- set_fact:
    Fruits:
      Apple: 'Red'
      Orange: 'Orange'
      Grapes: 'Greem'
 
- name: Ansible dictionary loop Example
  debug:
    msg: "Key is {{ item.key}} and color is {{item.value}}"
  with_dict: "{{ Fruits }}"
 
 
- set_fact:
    myvar: "hello1"
 
- name: Ansible loop with conditional example
  debug:
    msg: "{{ item }}"
  with_items:
    - "hello1"
    - "hello2"
    - "hello3"
  when: item == "{{ myvar }}"
 
 
- "{{ 'python-mysqldb' if ansible_distribution_version is version('18.00','<') else 'python3-mysqldb' }}"
- "{{ 'python-pymysql' if ansible_distribution_version is version('18.00','<') else 'python3-pymysql' }}"
 
 
- name: Extract database port
  set_fact:
    nova_database_port: "{{ nova_database_address.split(':')[1] | default(default_mysql_port) }}"
 
 
# group_by
https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#handling-os-and-distro-differences
- set_fact:
    group_extra_vars: "{{ os_env }}.{{ inventory_hostname.split('-')[1] | regex_replace('[0-9]', '') }}.{{ ansible_distribution_release }}.{{ ansible_product_name | replace(' ','-') | lower }}"
 
- name: Load extra vars
  group_by:
    key: "{{ group_extra_vars }}"
  tags: always
 
 
   - name : Find files bigger than 100mb in size
     find:
       paths: /var/log
       file_type: file
       patterns: 
         - '^[a-z]*_[0-9]{8}\.log$'
         - '^_[0-9]{2,4}_.*.log$'
         - '^[a-z]{1,5}_.*log$'
       size: 100m
       use_regex: yes
     register: output

Parted

- name: "Create partitions on devices"    
  block:   
    - name: install parted
      package:
        name: parted
        state: present 
 
    - name: "Read device information /dev/sda"
      parted: 
        device: "/dev/sda"
        unit: MiB
      register: device_info
 
    - name: "Add new partition /dev/sda2"
      parted: 
        device: "/dev/sda"
        number: "2"
        part_type: primary
        flags: [ lvm ]
        state: present
        part_end: "100%"
        part_start: "{{ device_info.partitions[0].end + 1}}MiB" 
 
    - name: "Add device to exising volume group {{ volumeGroup }}."
      lvg:
        vg: "{{ volumeGroup }}"
        pvs: "/dev/sda1,/dev/sda2"

Json

    - command: lshw -json -c bus
      register: lshw_json
 
    - debug:
        msg: "{{ ( lshw_json.stdout | from_json | to_nice_json ) }}"
 
    - set_fact:
        motherboard: "{{ (lshw_json.stdout | from_json)[0].product }}"

Jinja2

# format long Jinja2 expression 
- set_fact:
    ip_address: >-
      {{ ( api_response
           | map(attribute='fixed_ips')
           | map('from_json')
           | selectattr('ip_address', 'match', '^192')
           | list
           | first
          ).ip_address
      }}
 
  # Recursively find /tmp files older than 4 weeks and equal or greater than 1 megabyte
  - find:
      paths="/tmp"
      age="1d"
      size="1m"
      recurse=yes
security.nesting: "{{ 'true' if item in ['nomad-client1', 'nomad-client2', 'nomad-client3'] else 'false' }}"
- name: Retry download file from URL
  get_url:
    url: https://example.com/path/to/file
    dest: /path/to/target
  register: exporter_compressed_download
  until: exporter_compressed_download.status_code == 200
  retries: 60
  delay: 5
# keep line break
lookup('file', '/etc/foo.txt', rstrip=False)
 
# lookup
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/url_lookup.html
# split util last part and join
my_string.split(".")[:-1] | join

APT

- name: Add Percona Ubuntu focal repository
  apt_repository:
    repo: deb [arch=amd64] http://repo.example.com/foo/apt focal main
    update_cache: yes
 
- name: Install libssl1.1 package
  apt:
    deb: http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1l-1ubuntu1.3_amd64.deb
    update_cache: yes
  when: (ansible_lsb.major_release | int > 20)
 
- name: Enable universe repository
  apt_repository:
    repo: deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe

ansible_loop
https://blog.ordix.de/neuerungen-in-ansible-ab-version-2-8
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html#extended-loop-variables
https://stackoverflow.com/questions/29276198/ansible-how-to-construct-a-variable-from-another-variable-and-then-fetch-its-v
https://docs.ansible.com/ansible/latest/collections/ansible/utils/index_of_lookup.html

- name: "Get pevious / next control element"
  set_fact:
    prev: "{{ ansible_loop.previtem | default(ansible_loop.allitems[ansible_loop.length | int -1]) }}"
    next: "{{ ansible_loop.nextitem | default(ansible_loop.allitems[0]) }}"
  loop: "{{ groups['control'] |  select('search', inventory_hostname.split('-')[0]) | sort }}"
  when:
    - element == inventory_hostname
  loop_control:
    extended: yes
    loop_var: element
 
- set_fact:
    ctl_nodes: "{{ groups['control'] | sort }}"
 
- set_fact:    
    current_ctl_node_id: "{{ lookup('ansible.utils.index_of', ctl_nodes, 'eq', inventory_hostname) }}"
 
- debug:
    msg: "{{ lookup('ansible.utils.index_of', groups['control'], 'eq', inventory_hostname) }}"
 
- debug:
    msg: "current_ctl_node_id: {{ current_ctl_node_id }}"
 
- debug:
    msg: "{{ ctl_nodes[current_ctl_node_id | int - 1] }} / {{ ctl_nodes[current_ctl_node_id | int + 1] }}"
 
# nested / double loop
- name: Deploy SSH key for CI/CD users
  copy:
    src: ~/.ssh/{{ item[1] }}
    dest: /home/{{ item[0] }}/.ssh/
    owner: "{{ item[0] }}"
    group: "{{ item[0] }}"
    mode: 0600
  with_nested:
    - [ gitlab-runner, cicd ]
    - [ id_rsa, id_rsa.pub ]

Restart SSH connection

- name: reset ssh connection
  meta: reset_connection