Dynamic value assignment to ansible fact based on condition

I have a specific scenario that I’m thinking to implement. Let’s say I have a playbook that takes user input for something like a “Group name”. Each time the playbook runs, 1 group name is captured and added to a cached list. The tricky part is that I want every group name to be unique, but I don’t want to bother the user with the “unique” requirement. For example, if they want their group name to be “group” and there’s already a “group-1”, then they’ll simply be added to the list as “group-2”.

So, to start, I create an empty list (my_list) with the condition that it hasn’t been previously defined (so this step only executes the first time the playbook is run).
Then I take USER INPUT and register that user_input to a group_name var.

To achieve group name uniqueness, I’m thinking to use some numerical suffix like my previous example.
Maybe implement something like:

N = 1
unique_group = group_name + str(N)
WHILE unique_group IN my_list
N += 1
unique_group = group_name + str(N)
END LOOP
(at this point, we’ve ensured that we have a unique_group that is not in my_list)

So now we append the unique group to the cached list:
SET FACT my_list: “{{ my_list + [unique_group] }}” cacheable: yes

Each time the playbook is run, it prompts the user for a group name, and it appends a suffix to that group name that ensures it is unique, and saves it to a cached list. During the playbook execution, it starts with “Some-Group-Name-1” and checks the cached list to see if it’s been used already. If it’s in the list, then it checks for “Some-Group-Name-2” and continues until it has a name that hasn’t been used, then proceeds to add that group name to the cached list.

Hopefully that was a clear enough explanation between what I’m hoping to achieve and my current thought process for how to achieve it.

I have been working with Ansible for a few months learning bits and pieces at a time. This particular solution is more complicated than what I normally work with. Is Ansible capable of doing this type of algorithm? Am I overthinking it? I’ve been troubleshooting this for a few days and I’m not making great progress. I can see that Ansible is powerful and I assume it can handle this situation, but I’m struggling to figure out how to implement it. Do I need to use specific collections or plugins?

Or would this be something that I write in a py script, and I have Ansible run the script passing the args and registering the output?

Any feedback is certainly appreciated. Thanks!

2 Likes

Hi,

It’s definitely something you can do with Ansible although your playbook might become kind of a mess :stuck_out_tongue:

I’d say if you value readability and simplicity, use something else (a simple bash or python script would be more appropriate for this kind of logical flow IMO, though I really like to bend Ansible for this kind of stuff :wink: ), if not, a bit of Jinja magic will do the trick.

I’ll see if I can figure something out on the upcoming week-end.

2 Likes

Thanks! Readability is a concern, so I was thinking to try a Python script approach. Although you mention “jinja magic”, and that piques my interest. I’ll take a closer look at that as well. Thank you.

2 Likes

Do you particularly care about the group name, other than keeping it to verify that a new one made would be unique?

If the goal is purely to make sure it is unique, check out unix epoch time. I use that as the random variable in a python script I use - to define a username, I take the lowercase versions of the first letter of the users first name and last name, and then add the current the epoch time. Since the epoch time changes every second, it will always make the username unique. For me, I don’t care what the username is, because it is temporary and gets automatically deleted in a couple of weeks. So I don’t need to know it or track it anywhere.

I haven’t done it in ansible, but since ansible is python-based, I assume it’s possible to do it in a playbook. I’m not at my computer right now, but if you end up needing the python code I used, let me know. It was very simple to find online.

2 Likes

That sounds like a possibility. The “group name” will be specific, the suffix just needs to make it unique. I’ll think about this one for a little bit. Thanks for the input!

I was able to create a python script to generate the unique suffix as I had described (incrementing “N”), and utilized Ansible’s “script” module to execute it successfully, as suggested by Pierre.

My issue with the built-in script module, for this particular use case, is that one of my command line args being passed to the script is a List, but the script module instead passes the list as a bunch of strings with unintended characters added (like commas, etc.) so I have to loop through and eliminate those chars and recreate the list.

After doing this exercise, it seems the next logical step would be to convert my Python script into a customized Ansible module, which will hopefully address my issue with the command line args because Ansible will let me control the data type.

Thanks again for your feedback with this! It was very helpful.

2 Likes

Glad you figured it out ! :slight_smile:

My issue with the built-in script module, for this particular use case, is that one of my command line args being passed to the script is a List, but the script module instead passes the list as a bunch of strings with unintended characters added (like commas, etc.) so I have to loop through and eliminate those chars and recreate the list.

I think I had a similar issue a few years back, but I can’t find any related config in my roles and playbooks, and my memory is really bad :confused:

After doing this exercise, it seems the next logical step would be to convert my Python script into a customized Ansible module

Although I think most of the times writing their own modules is overkill, it makes total sense for this kind of usecases. It is even stated on script module page !

I still plan to have a go and try to find a pure Ansible (as in existing modules and templating) solution this week-end if I can find some time to work on it; will report if that worked.

Wish you all a nice week-end !

1 Like

You can do it in one line of Jinja. The code below generates a list of names with integer suffixes, then shows step by step how the expression works.

---
- name: Experiment with integer suffixes
  hosts: localhost
  gather_facts: false
  vars:
    bases: ["alpha", "beta", "iota"]
    batches: '{{ range(0, 300, 17) | batch(bases|length) }}'
    names: '{{ batches | map("zip", bases) | flatten(1) | map("reverse") | map("join", "_") | shuffle }}'
  tasks:
    - name: Show our bases
      ansible.builtin.debug:
        var: bases
      # bases: [ "alpha", "beta", "iota" ]

    - name: Show our batches
      ansible.builtin.debug:
        var: batches
      # batches: [ [ 0, 17, 34 ], [ 51, 68, 85 ], [ 102, 119, 136 ], [ 153, 170, 187 ], [ 204, 221, 238 ], [ 255, 272, 289 ] ]

    - name: Show our random names
      ansible.builtin.debug:
        var: names
      # names: [ "beta_119", "iota_289", "beta_170", "alpha_51", "beta_17", "alpha_0", "iota_34", "beta_272", "alpha_102",
      #          "alpha_204", "iota_85", "alpha_255", "beta_221", "beta_68", "iota_136", "alpha_153", "iota_238", "iota_187" ]

    - name: Show our random "beta_" names
      ansible.builtin.debug:
        msg: '{{ names | select("match", "beta_") }}'
      # msg: [ "beta_272", "beta_17", "beta_221", "beta_119", "beta_170", "beta_68" ]

    - name: Show sorted "beta_" ints
      ansible.builtin.debug:
        msg: '{{ names | select("match", "beta_") | map("regex_replace", "beta_(\d+)$", "\1") | map("int") | sort }}'
      # msg: [ 17, 68, 119, 170, 221, 272 ]

    - name: Largest "beta_" int
      ansible.builtin.debug:
        msg: '{{ names | select("match", "beta_") | map("regex_replace", "beta_(\d+)$", "\1") | map("int") | sort | last }}'
      # msg: "272"

    - name: Next unused "beta_" int
      ansible.builtin.debug:
        msg: '{{ (names | select("match", "beta_") | map("regex_replace", "beta_(\d+)$", "\1") | map("int") | sort | last) + 1 }}'
      # msg: "273"

    - name: Next beta_
      ansible.builtin.debug:
        msg: '{{ "beta_" ~ ((names | select("match", "beta_") | map("regex_replace", "beta_(\d+)$", "\1") | map("int") | sort | last) + 1) }}'
      # msg: "beta_273"
4 Likes

Sorry for the delay; busy week :confused:

Here is an awful mess that kind of does what you’re looking for, relying solely on Jinja2 filters and a bit of logic using inline Jinja2 in yaml folds. I’m pretty confident it can be improved in many ways.

---

- name: Add new group to groupslist, ensuring group names are unique
  gather_facts: false
  connection: local
  hosts: localhost
  become: false
  vars:
    groupslist: "{{ hostvars['localhost']['groupslist'] | d([]) }}" # ANSIBLE_CACHE_PLUGIN=jsonfile ANSIBLE_CACHE_PLUGIN_CONNECTION=/tmp/groupslist.json
  tasks:
    - name: Get user input
      when: _groupname_input is undefined
      ansible.builtin.pause:
        prompt: |
          ==============================================================================================================
            Group Name ?
          ==============================================================================================================
      register: _groupname_input
      delay: 1
      retries: 999

    - name: Register last existing group name matching pattern
      ansible.builtin.debug:
        msg: "{{ groupslist | map('regex_findall', '(^' + _groupname_input.user_input|lower + '(?:_[0-9]+)?$)') | flatten | unique | last |d() }}"
      register: _last_matching_groupname
      failed_when: false
      no_log: true

    - name: Define new group name based on last existing group name pattern
      when: _last_matching_groupname.msg|length
      ansible.builtin.set_fact:
        _newgroup: >-
          {%- set _last_matching_groupname_nb = (_last_matching_groupname.msg|lower).split('_')[-1] | regex_search('^[0-9]+$') | string -%}
          {%- if _last_matching_groupname_nb | regex_search('^[0-9]+$') -%}
              {%- set _newgroup_nb = (_last_matching_groupname_nb | int + 1) | string -%}
              {{ _last_matching_groupname.msg|lower | regex_replace('(_[0-9]+$)', '_' + _newgroup_nb) }}
          {%- else -%}
              {{ _last_matching_groupname.msg|lower + '_1' }}
          {%- endif -%}

    - name: Store new (unique) group in var
      when: _newgroup|d() not in groupslist
      ansible.builtin.set_fact:
        groupslist: "{{ groupslist + [_newgroup | d(_groupname_input.user_input)] }}"
        cacheable: true

    - name: Print groupslist content
      ansible.builtin.debug:
        msg: "{{ groupslist }}"
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.