Ansible Adventures

Not sure what you mean. Can you provide an example?

1 Like

vars file:

LAX:
  name: lax
  vcnetuse: lax1051
  datacenter: LAX
  server: vclax1.example.com
  cluster: vclax1-linux-cluster1
  ds0: vclax1-linux-cluster1-datastore1
  ds1: Unity2LAX_Datastore13
  ds2: Unity2LAX_Datastore06
  ds3: Unity2LAX_Datastore02
  isofull: null

CLT:
  name: clt
  vcnetuse: clt1051
  datacenter: VCCLT1
  server: vcclt1.example.com
  cluster: vcclt1-linux-cluster1
  ds1: vblk4_vmfs3
  ds2: null
  ds3: null
  isofull: null

Say all my included tasks are using server/datacenter/cluster var names.

Say i want to use the vars from CLT ā€¦ if i want to sent the VM to CLTā€¦
how can i control which group of vars is used

(i already ended up using lookup plugin) but i feel like theres something obvious im missing

1 Like

Sounds like it should be handled in inventory and not in a vars file. Control the values by changing the group membership of the VM and then re-run the playbook. Not sure though without broader context.

---
# inventory.yml
all:
  children:
    lax:
      children:
        vclax1_linux_cluster1:
          hosts:
            vclax1.example.com:
    clt:
      children:
        vcclt1_linux_cluster1:
          hosts:
            vcclt1.example.com:
---
# group_vars/lax.yml
datacenter:
  vcnetuse: lax1051
  name: lax
---
# group_vars/vclax1_linux_cluster1.yml
cluster:
  ds0: vclax1-linux-cluster1-datastore1
  ds1: Unity2LAX_Datastore13
  ds2: Unity2LAX_Datastore06
  ds3: Unity2LAX_Datastore02
  isofull: null

etc.

Or something along those lines.


Bonus, if you need to take a variable from a datacenter other than the one the host is currently in:

# host in the clt group
- debug:
    msg: "{{ hostvars[ groups['lax'][0] ]['datacenter']['name'] }}

Or you can set all of the datacenters and clusters in a dictionary in the same vars file that you already have (with the same inventory file from above):

---
# vars/main.yml
datacenters:
  LAX:
    name: lax
    vcnetuse: lax1051
    datacenter: LAX
    server: vclax1.example.com
    cluster: vclax1-linux-cluster1
    ds0: vclax1-linux-cluster1-datastore1
    ds1: Unity2LAX_Datastore13
    ds2: Unity2LAX_Datastore06
    ds3: Unity2LAX_Datastore02
    isofull: null
  CLT:
    name: clt
    vcnetuse: clt1051
    datacenter: VCCLT1
    server: vcclt1.example.com
    cluster: vcclt1-linux-cluster1
    ds1: vblk4_vmfs3
    ds2: null
    ds3: null
    isofull: null
---
# group_vars/lax.yml
dc: LAX
---
# play.yml
...
- debug:
    var: datacenters[dc]
2 Likes

Thank you for the suggestion(s)ā€¦
i rewrote some stuff
and your post helped me a ton
thank you

2 Likes

One other note on this that I didnā€™t realize for a long time. Ansible will populate the hostvars dictionary for all hosts in the inventory even if they are not in the current play.

2 Likes

@nx2l have you written any plugins for Ansible? I want to extend the first_found lookup. I have written a role that does what I want, but I know it should really be a plugin instead.

I am familiar with Python but havenā€™t written much. Looking at the developer docs, I donā€™t understand what some of the boilerplate is doing and Iā€™m not confident with error handlingā€¦

https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#lookup-plugins

2 Likes

sadly no, i find it hard finding info on the built in pluginsā€¦

fyi

the lookup plugin for fileā€¦ you can use {{ var }} in the file nameā€¦ couldnt find any examples show vars, but some trail and errorā€¦ and it works.

I have a python book that i havent finished reading and I bet if i did, i could write a plugin if needed

fyi this one
image

1 Like

No Starch Press is a good publisher. I havenā€™t read that one, but Iā€™ve used other books from them before and theyā€™re often pretty good ones.

1 Like

I also have it.

Yeah so I use a formulation of that where it looks for a tasks, vars or template file based on certain attributes of the host. For instance, on a Rocky 8.4 host, it will look for prefix_rocky_8.4.yml, then prefix_rocky_8.yml, then prefix_rocky.yml, then prefix_redhat.yml, then prefix_linux.yml, then prefix_ssh.yml, etc. And itā€™s actually more complicated than that because it tracks selinux vs app armor, NetworkManager vs networkd.

Hereā€™s the whole thing if youā€™re curious. I compressed it into one giant task because I use it so liberally it makes the Ansible output extremely noisy otherwise.

---
# vim: ts=2:sw=2:sts=2:et:ft=yaml.ansible
#
# Get the first found name of a tasks, variables or template file
#
########################################################################

# Condense into one task to be as quiet as possible
- name: >-
    Look up first found {{ first_found['type'] }} in
    {{ ff_candidates_var['paths'][0] }}
  ansible.builtin.set_fact:
    ff: "{{ lookup('first_found', ff_candidates_var) }}"

  # Fail if the first_found variable isn't formatted properly.
  failed_when: >-
    first_found | type_debug != 'dict'
    or  first_found.keys()
        | difference( [ 'type', 'prefix', 'suffix', 'default',
                        'allow_none' ] )
        != []
    or first_found['type'] not in ['vars', 'tasks', 'templates']
    or  first_found['prefix'] is defined
        and not first_found['prefix'] | string
    or  first_found['suffix'] is defined
        and not first_found['suffix'] | string
    or  first_found['default'] is defined
        and first_found['default'] | string
    or  first_found['allow_none'] | type_debug != 'bool'

  vars:

    # Parameters may be passed in using first_found, first_found_item
    # or first_found_var, or the default (defined in defaults/main.yml)
    # will be used if none of those exist. If more than one is present,
    # precedence follows the order listed.
    first_found: >-
      {{  first_found_default
          | combine(  first_found_var
                      | default(first_found_item)
                      | default({}) ) }}

    # Extensions are defined in default/main.yml
    ext_var: "{{ exts[ first_found['type'] ] }}"

    # Precedence follows most specific to least specific. Since
    # ansible_connection is used, this role may be useful even before
    # facts are gathered (if running against hosts with a variety of
    # connection types).
    ff_candidates_var:
      files:
        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{ ansible_distribution_version | default(omit) | lower }}_\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8.5_nmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{ ansible_distribution_version | default(omit) | lower }}_\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8.5_selinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{ ansible_distribution_version | default(omit) | lower }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8.5SUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{  ansible_distribution_major_version
              | default(omit)
              | lower }}_\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8_nmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{  ansible_distribution_major_version
              | default(omit)
              | lower }}_\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8_selinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}-\
          {{  ansible_distribution_major_version
              | default(omit)
              | lower }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky-8SUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}_\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky_nmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}_\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrocky_selinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_distribution | default(omit) | lower }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrockySUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_os_family | default(omit) | lower }}_\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXredhat_nmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_os_family | default(omit) | lower }}_\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXredhat_selinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_os_family | default(omit) | lower }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXredhatSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_system | default(omit) | lower }}_\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXlinux_nmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_system | default(omit) | lower }}_\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXlinux_selinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_system | default(omit) | lower }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXlinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ net_mgr | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXnmSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ mac | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXselinuxSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_network_os | default(omit) | split('.') | last }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXrouterosSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{ ansible_connection | default(omit) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXsshSUFFIX.EXT

        - "{{ first_found['prefix'] | default('') }}\
          {{  first_found['default'] | default(omit, true) }}\
          {{ first_found['suffix'] | default('') }}.\
          {{ ext_var }}"
          # Ex: PREFIXdefaultSUFFIX.EXT

        - "{{ first_found['allow_none']
              | ternary('/dev/null', omit) }}"

      paths:
        - "{{ ansible_parent_role_paths
              | difference( [role_path] ) | first }}/\
          {{ first_found['type'] }}/"

Usage looks like this:

- name: Use the first found role to get platform-specific file name
  ansible.builtin.include_role:
    name: o0_o.first_found
  vars:
    first_found_var:
      type: tasks
      prefix: cfg_iface_vlan_
      allow_none: true

- name: Configure VLAN child interfaces
  ansible.builtin.include_tasks: "{{ ff }}"
  loop: "{{ iface['vlans'] | default([]) }}"
  loop_control:
    loop_var: vlan_subnet_item
1 Like

I had an issue with a dict variable set in group_vars. The dict needs to be updated by hosts during the play. The issue is keeping it in sync across all hosts in the group.

- set_fact:
    group_foo: "{{ group_foo | combine( host_bar ) }}"

Assuming host_bar is different for each host, this will result in a group_foo that is no longer the same for the whole group.

I have solved this by running my tasks file in pseudo-serial with this loop/conditional:

  when: inventory_hostname == host_item
  loop: "{{ ansible_play_hosts }}"
  loop_control:
    loop_var: host_item

And then running this at the end of that tasks file:

- name: Propagate group_foo
  set_fact:
    group_foo: "{{ group_foo }}"
  delegate_to: "{{ foo_host_item }}"
  delegate_facts: true
  loop: "{{ groups['foo']] }}"
  loop_control:
    loop_var: foo_host_item

Note that {{ group_foo }} is taken from the current host, despite delegate_facts. The delegation does affect the assignment though, so itā€™s taking the current value of group_foo and applying it to all other hosts in the group.

A quirk I discovered while working on this is that when looping a single task, hostvars is not updated until the entire loop has executed. So if youā€™re drilling into the hostvars dictionary in a set_fact loop with delegation, hostvars will not reflect changes made during the loop. Again, this only applies to looping on a single task. To work around it, put your task in a file and loop include_tasks instead.

1 Like

What is the difference between the iterable and sequence jinja2 tests? When would you use on over the other?

anyone know if its possible to import a playbook based on a group name?

something like this

- name: test
  import_playbook: {{  item }}.yaml
  when: {{ item }}.yaml exists
  with_items: "{{ group_names }}"
1 Like

Yeah it should be.

Use a lookup for this part.

1 Like

do i need quotes?

trying this next.

image

waitā€¦ wouldnt that load the contents of the file ā€¦ not just check if it exists?

Yeah, file gives the contents, but could ignore errors and set the condition to it not being an empty string.

when: "{{ lookup('file', item + '.yaml' , errors='ignore') != '' }}"

Might be easier to use pipe though.

when: "{{ lookup('pipe', '-e ' + item + '.yaml') }}"

And yeah, your formatting isnā€™t right with the variable. No need to nest {{ }}.

I didnā€™t test either of these but should give you the idea.

Now that I think about it, Iā€™m not sure how pipe handles exit codes, so maybe stick with file.

2 Likes

I found fileglobā€¦ looking for examples on it right now.

2 Likes

good news i figured out how to make the listā€¦
the bad news it it doesnt work

ERROR! ā€˜list_of_exist_group_playbksā€™ is undefined

or

ERROR! ā€˜delegate_factsā€™ is not a valid attribute for a PlaybookInclude

or

ERROR! ā€˜loopā€™ is not a valid attribute for a PlaybookInclude

1 Like

guessing these arent any help

Template Designer Documentation ā€” Jinja Documentation (3.1.x)

Template Designer Documentation ā€” Jinja Documentation (3.1.x)

1 Like
- set_fact:
  list_of_exist_group_playbks: "{{ list_of_exist_group_playbks | default([]) + [ item ] }}"

You donā€™t need run_once. Try wrapping wrapping the import in a tasks file and looping on the include_tasks.

Use with_items: "{{ list_of_exist_group_playbks | intersect( lookup('fileglob', '*.yaml') ) }}"

Does fileglob give you full or relative paths? Is it a list?

2 Likes

wellā€¦ if im running big group of systems that have multiple groupsā€¦ i dont want a playbook meant for a whole group to kick off 20 times b/c 20 systems in the same group ran before the import, right

yes
and yes

will try the task calling the import laterā€¦

if task calling import doenst workā€¦ iā€™ll see about convert the playbook to a task that can be includedā€¦

1 Like