- Log in to post comments
# 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
Create splitted CEPH OSD LVs
- name: Create splitted CEPH OSD LVs lvol: vg: ceph{{ item.0 }} lv: ceph-block_data{{ item.1 }} size: 50%VG with_together: - "{{ range(1, (storage_disks_result.files | length) + 1) | list + range(1, (storage_disks_result.files | length) + 1) | list }}" - "{{ range(1, (storage_disks_result.files | length) * 2 + 1, 2) | list + range(2, (storage_disks_result.files | length) * 2 + 2, 2) | list }}"