------------------------------------------------------------------------------- Jinja2 Code (Ansible values and templating) ------------------------------------------------------------------------------- Using defaults tasks: -name a task ansible.builtin.file: ... mode: '{{ variable | default(omit) }}' ... The "defailt(omit)" is special in that it will make the varible optional and so the use the system default, as if 'mode:' in the above was NOT provided. See: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_filters.html#making-variables-optional ------------------------------------------------------------------------------- Disable jinja in a template {% raw -%} ... {% endraw -%} ------------------------------------------------------------------------------- Conditional boolean that: not file_stat.stat.exists # file does not exist value when: 'inventory_hostname == "sign-on.example.com"' when: '"-tst-" not in inventory_hostname' when: '"-prd-" in inventory_hostname' group when: 'inventory_hostname in ["hosta", "hostb"]' when: 'inventory_hostname_short in ["shrek", "bigdata"]' when: '"env_production" in group_names' when: '"env_development" in group_names' when: '"os_rhel_9" in group_names' time when: '{{ now() < "2023-03-28 00:00:00" | to_datetime }}' ------------------------------------------------------------------------------- Ternary Conditional (one-line if) vars: service_enable: true ... # Classical 'python' if-else solution - ansible.builtin.service: state: '{{ "start" if service_enable else "stop" }}' # Chained if-else a=1; b=2 order: 1 if a > b else -1 if a < b else 0 # order => -1 # *** This is better *** # The 'not' puts truth first, and ensures input is a boolean (0,1) index state: '{{ ("start", "stop")[not service_enable] }}' # Explict dictionary lookup state: '{{ {True:"start", False:"stop"}[service_enable] }}' # Expanded indexed dictionary (multiple values) type: '{{ {"apples":"fruit", "carrot":"vegie"}[item] }}' # And-Or # This has a clear left to right ordering of actions # BUT only works if the resulting value for 'true', is itself true # or you will always get the false value. # state: '{{ service_enable and "start" or "stop" }}' # Work around for a possible 'false on true' problem # EG: true -> 0 false -> 10 value: '{{ (service_enable and [0] or [10])[0] }}' ------------------------------------------------------------------------------- Time Handling... Getting the current hour! {{now.convertToTimeZone("Australia/Brisbane").format("H").asNumber}} Time/Date based check # Temporary access until 28/02/23 # You only need to re-run playbook to action! temporary_sudo_access: '{{ now() < "2023-02-28 00:00:00" | to_datetime }}' ------------------------------------------------------------------------------- Conversion Loop over a list to convert to different list sshd_users: >- {%- set sshd_users = {} -%} {%- for user in sftp_clients -%} {%- if user.state | default("present") == "present" -%} {{- sshd_users.update({ user.name: { "AuthorizedKeysFile": ".ssh/authorized_keys", "PubkeyAcceptedKeyTypes": "+ssh-ed25519,ssh-rsa", "PasswordAuthentication": "yes" } }) -}} {%- endif -%} {%- endfor -%} {{- sshd_users -}} Flatten multiple lists into a list vars: main_systems: - 192.168.1.1 - 192.168.1.2 - 192.168.1.3 user_systems: - 192.168.1.11 - 192.168.1.12 - 192.168.1.13 # Simple Append... both_systems: '{{ main_systems + user_systems }} # Merge (with duplicate removal)... both_systems: '{{ main_systems | union( user_systems ) }} Join a list of items into a comma seperated string vars: ssh_hosts: - 192.168.1.1 - 192.168.1.5 - 192.168.1.13 nft_input_group_rules: 100_ssh: - 'tcp dport ssh ip saddr { {{ ssh_hosts | join(", ") }} } accept' OR into a new list vars: relay_ports: '4000-4009' relay_ips: - '132.234.28.0/24' # s3 staff workstations - '132.234.23.0/24' # AppDev Staff workstations (Raylander Cardoso) - '10.250.254.1/32' # Nagios port testing relay_nftable_rules: 120_nftable_rules: >- {%- set rules = [] -%} {%- for ip in relay_ips -%} {{ rules.extend( [ 'tcp dport '+relay_ports+' ip saddr '+ip+' accept' ] ) }} {%- endfor -%} {{ rules }} ------------------------------------------------------------------------------- Regex Expression matching (verson extraction) - name: java version check ansible.builtin.command: cmd: /opt/CA/WA_Agent_DEV/SystemAgent/AGENT_DEV/jre/bin/java --version register: jre_installed check_mode: false # Run in check mode failed_when: false # A fail is itself part of the test changed_when: false # No a change, just a test - name: regex match ansible.builtin.debug: msg: '{{ jre_installed.stdout_lines | default(["0.0-0"]) | map("regex_search","[0-9]+\.[0-9.-]+") | first }}' ------------------------------------------------------------------------------- OS Version checking gu_ansible_autopatch_type: >- {%- if ansible_os_family == "RedHat" and ansible_distribution_major_version is version("9", ">=") -%} ansible {%- else -%} cron {%- endif -%} OR {%- set rhel9 = ansible_facts.distribution == "RedHat" and ansible_facts.distribution_major_version is version("9", ">=") -%} ... '{{ "it is rhel9" if rhel9 else "pre-rhel9" }}' OR when: "os_rhel_9" in group_names ------------------------------------------------------------------------------- Multi-line Jinja Expressions - name: get week ansible.builtin.set_fact: week: >- {%- set date = now() -%} {%- set dom = date.strftime("%d") | int -%} {%- set dow = date.strftime("%w") | int -%} {%- set prev_sun_epoch = date.timestamp() - dow * 24*60*60 -%} {%- set prev_sun_date = "%Y-%m-%d %H:%M:%S" | strftime(prev_sun_epoch) | to_datetime -%} {%- set year = prev_sun_date.strftime("%Y") | int -%} {%- set mon = prev_sun_date.strftime("%m") -%} {%- set first_sun_day = ("{}-{}-01 00:00:00".format(year, mon) | to_datetime).strftime("%d") | int - ("{}-{}-01 00:00:00".format(year, mon) | to_datetime).strftime("%w") | int + 7 -%} {%- set first_sun_date = "{}-{}-{} 00:00:00".format(year, mon, first_sun_day) | to_datetime -%} {# week is 1-5 #} {%- set week = (prev_sun_date - first_sun_date).days / 7 + 1 -%} {{ week | int }} Jinja has filters, e.g. {{ foo | upper }}. But it's Python underneath, so each return value has all of the usual Python methods available to it, That is it becomes... {{ foo.upper() }} because strings have "upper" method packages: >- {%- set packages = [ "bash-completion", "lsof", "ltrace", "lvm2", ] -%} {%- if ansible_facts.distribution == "RedHat" and ansible_facts.distribution_major_version is version("9", "==") -%} {{ packages.extend( [ "bind-utils", "perl-interpreter" ] ) }} {%- endif -%} {%- if ansible_facts.distribution == "RedHat" and ansible_facts.distribution_major_version is version("8", "==") -%} {{ packages.extend( [ "bind-utils", "dnf-utils", ] ) }} {%- endif -%} {{ packages }} -------------------------------------------------------------------------------