How to iterate through survey response

Apologies for the noob question, but my playbook makes an API call to our cloud WAF to write http redirects. It works really well based on three survey questions:

  1. domain
  2. url_from
  3. url_to

The problem is that I’d like to allow the person running the playbook to put in multiple sets of variables in that group so they can write more than one redirect at a time. How could I get it to run so that if there are only one set of entries for 1-3 then run and stop there, but if there are more entries have the playbook iterate over the other entries?

Change your From and To inputs to a single textbox, call the variable redirects.
Have them input the from and to with a delimiter.

Something like thsi maybe:the_from_url||the_to_url

Then, iterate through them and slit them for whatever you want to use them for.

# Sample task that iterates and splits the input textbox
- name: Do Something Fun
  ansible.builtin.debug:
    msg: "This URL {{ item | split('||') | first }} redirects to {{ item | split('||') | last }}."
  with_items: "{{ redirects | split('\n') | default([])

Thanks for the reply Dustin,
Interesting thought, but I don’t see the value in combining two inputs into one field. Additionally I’m not quite sure how to handle the survey at that point since all survey’s require a unique variable in Ansible Tower. I was curious if I could leverage a dictionary to define a unique variable for the survey, but allow it to iterate over the same parent value. For example something like:
survey_vars:
domain:
- domain1: “{{ domain1}}”
- domain2: “{{ domain2}}”
- domain3: “{{ domain3}}”
from_url:
- from_url1: “{{ from_url1 }}”
- from_url2: “{{ from_url2 }}”
- from_url3: “{{ from_url3 }}”
to_url:
- to_url1: “{{ to_url1 }}”
- to_url2: “{{ to_url2 }}”
- to_url3: “{{ to_url3 }}”
I’m open to any ideas, but I’m not very strong in Ansible so please dumb it down for me, thanks.

By combining two inputs to single input, you achieve:

  • You only need one iteration loop and jinja2 template easily processes each line in the iteration.
  • It helps enforce user behavior. Users more easily see the structure of the required data, instead of hoping they put the same number of URLs in the correct order in two different input fields.

If you really do want to keep it as two different variables, I would:

  • Still use text area and have the URLs entered one line at a time (easier for humans to process)
  • Have one playbook convert the inputs from a string to a list
  • Have a second playbook iterate over one of the lists, track the location in the loop, and reference the same location in the other list when calling the command.
# Assume all other playbook setup stuff is above and these are your tasks
- name: Normalize Input Variables:
  ansible.builtin.set_fact:
    from_list: "{{ from_urls | split('\n') | default([]) }}"
    to_list: "{{ to_urls | split('\n') | default([]) }}"

- name: Execute some commands with matching inputs
  ansible.builtin.debug:
    msg: "From: {{ item }}\tTo: {{ to_list[index] }}"
  with_items: "{{ from_list }}"
  loop_control:
    index_var: index

Normalize Input Variables

set_fact sets the value of the specified variables

from_list: "{{ from_urls | split('\n') | default([]) }}" Reads the Survey Variable from_urls, splits it into a list using a newline \n as the delimiter, and defaults the variable to an empty list if the split should fail or there is no input

to_list: "{{ to_urls | split('\n') | default([]) }}" Reads the Survey Variable to_urls, splits it into a list using a newline \n as the delimiter, and defaults the variable to an empty list if the split should fail or there is no input

Execute some commands with matching inputs

We are just using a simple debug to print a message to the screen, but you can use the concepts to apply to any module or task that you need.

We are looping over the from_list list that we created in the first play. We are using loop_control and index_var to tell the loop to track the current index.

item is a built in variable for ansible loops. It references the current item in the list.

So, msg is set to a string that contains the current item in the loop iteration and the item in to_list that corresponds to the index of the current item.

Edit: Removed extraneous line in set_fact

I see where your going with that, but one thing that I’m confused on is since the variables need to be unique for the survey, in your code it’s iterating over the same var. Our goal is to create an Ansible Tower Playbook, that our less experienced engineers can use to write these http redirects, so when they fire off the playbook they will be prompted with the three input requests that are grouped: domain, from_url and to_url. To create the survey and keep unique vars, I created domain1, domain2, domain3, from_url1, from_url2, etc. That is why I was thinking that a dictionary may be the way to go since domain1-3 would be under domain, and from_url1-3 would be under from_url, etc. But i’m not quite sure how to implement that or if that is even a viable option.

Can you give me an example of one domain, one from_url, and one to_url? I’ll see what I can get spun up in my AWX lab after work today.

sure, the goal is when they run the playbook they get these prompts:
domain: domain name here (this forum would not allow me to put a domain in here)
from_url: https://example.com/site1
to_url: https://example.com/site2

Up to that point, it works amazingly well. Unfortunately, I’m really struggling to get it to work with multiple inputs. So we get requests from the business with 10-20 redirect requests for different domains sometimes. I’d like to give our team the opportunity to complete more than one redirect at a time

There is no way to do a dynamic number of inputs on a Survey, which I think is what you are trying to achieve. About the only way I can think of to make this more user friendly would be to have the user interface in another designed for end user interactions in mind, have that app sanitize your inputs, and then call the AWX API to execute the playbook.

However, I made a mock up of what you are looking for, in two different ways to see if either one is useful to you.

Method 1: Single Text Area Input

Survey Questions

Question 1 (The only question)

Question: List of Redirects
Description: List of redirects, one per line, using format “domain,from_url,to_url”
Answer variable name: redirect_list
Answer Type: Text area

Survey Input

I used this CSV list in the text area

mydomain.com,/myapp,/private/myapp
mydomain.com,/myapp1,/private/myapp1
mydomain.com,/myapp2,/private/myapp2
mydomain.com,/myapp3,/private/myapp3

image

Playbook

---
# Sample Playbook to show how to use the survey inputs
- name: Survey Sample - Single Input (TextArea)
  hosts: all
  
  tasks:
    - name: Display all variables
      ansible.builtin.debug:
        msg: "{{ data[0] }} :: Redirecting {{ data[1] }} to {{ data[2] }}"
      with_items:  "{{ redirect_list | default([]) }}"
      vars:
        data: "{{ item | split(',') | default([]) }}"

    # I don't know what your actual play will look like because I don't know
    #    API call you are actually making to your WAF, but here is an example
    - name: Add Redirect to WAF
      ansible.builtin.uri:
        url: "https://your.waf.url.com/api/redirect/endpoint"
        username: "{{ waf_username }}"
        password: "{{ waf_password }}"
        method: POST
        force_basic_auth: yes
        body_format: json
        body:
          domain: "{{ data[0] }}"
          from_url: "{{ data[1] }}"
          to_url: "{{ data[2] }}"
      with_items:  "{{ redirect_list | default([]) }}"
      vars:
        data: "{{ item | split(',') | default([]) }}"

Sample Output

$ ansible-playbook -i inventory play_testing.yml

PLAY [Survey Sample - Single Input (TextArea)] *********************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************
ok: [localhost]

TASK [Display all variables] ***************************************************************************************************************************
ok: [localhost] => (item=mydomain.com,/myapp,/private/myapp) => {
    "msg": "mydomain.com :: Redirecting /myapp to /private/myapp"
}
ok: [localhost] => (item=mydomain.com,/myapp1,/private/myapp1) => {
    "msg": "mydomain.com :: Redirecting /myapp1 to /private/myapp1"
}
ok: [localhost] => (item=mydomain.com,/myapp2,/private/myapp2) => {
    "msg": "mydomain.com :: Redirecting /myapp2 to /private/myapp2"
}
ok: [localhost] => (item=mydomain.com,/myapp3,/private/myapp3) => {
    "msg": "mydomain.com :: Redirecting /myapp3 to /private/myapp3"
}

PLAY RECAP *********************************************************************************************************************************************
nautobot.durstie.space     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

I cannot replicate what the output of the WAF updates would look like because I don’t have a WAF to run this against, and it’s too much effort for me to write up a quick http app to forge responses.

Method 2: Three Mapped Inputs

In my opinion, this method is more prone to human error.

Survey Questions

Question 1

Question: List of domains, separated by comma
Answer Variable: domain_list
Type: Text

Question 2

Question: List of from_urls, separated by comma
Answer Variable: from_list
Type: Text

Question 3

Qustion: List of to_urls, separated by comma
Answer Variable: to_list
Type: Text

Survey Input

domain_list: mydomain.com,mydomain.com,mydomain.com
from_list : /myapp,myapp1,/myapp2
to_list: /private/myapp,/private/myapp1,/private/myapp2,

image

Playbook

---
# Sample Playbook to show how to use the survey inputs
- name: Survey Sample - Single Input (TextArea)
  hosts: all

  tasks:
    - name: Normalize Input Data
      ansible.builtin.set_fact:
        domain_list: "{{ domain_list | split(',') | default([]) }}"
        from_list: "{{ from_list | split(',') | default([]) }}"
        to_list: "{{ to_list | split(',') | default([]) }}"

    - name: Display all variables
      ansible.builtin.debug:
        msg: "{{ item }} :: Redirecting {{ from_list[index] }} to {{ to_list[index] }}"
      with_items:  "{{ domain_list }}"
      loop_control:
        index_var: index

Sample Output

$ ansible-playbook -i inventory play_testing.yml

PLAY [Survey Sample - Single Input (TextArea)] *********************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************
ok: [localhost]

TASK [Normalize Input Data] ****************************************************************************************************************************
ok: [localhost]

TASK [Display all variables] ***************************************************************************************************************************
ok: [localhost] => (item=mydomain.com) => {
    "msg": "mydomain.com :: Redirecting /myapp to /private/myapp"
}
ok: [localhost] => (item=mydomain.com) => {
    "msg": "mydomain.com :: Redirecting /myapp1 to /private/myapp1"
}
ok: [localhost] => (item=mydomain.com) => {
    "msg": "mydomain.com :: Redirecting /myapp2 to /private/myapp2"
}

PLAY RECAP *********************************************************************************************************************************************
nautobot.durstie.space     : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

I didn’t include a sample URI call in the second example because it can be easily inferred based on the first example.