Chaitanya Mudike
Chaitanya Mudike

Reputation: 11

Passing Values in Variables to run specific commands in Ansible

I have a yml file which installs ODBC Connections on a windows nodes using powershell. For Each DB type we have different yml files and each yml has install scripts for different Databases.

So, everytime we want to change or add a new connection, we comment out all but the one that is only required to be executed and this requires a lot of manual work as and when a connection is changed or added.

This is how i defined the win_command:

- name: Run xxxx Oracle
  win_command: powershell -executionpolicy unrestricted folder:\xxx.ps1 -Server {{ XXXX_DB_SERVER }} -UID {{ XXXX_Username }} -PWD {{ XXXXX }} -Database {{ XXX_DB_NAME }} -DSN {{ XXXX_DSN }}
  register: cmdXXXXX
- debug: var=cmdXXXX

I have commands like this for almost 9 DBs in this yml. I want to create a variable which will be provided at runtime and only those commands should be executed to install the DB connections.

Upvotes: 1

Views: 1527

Answers (1)

Zeitounator
Zeitounator

Reputation: 44615

If you really have created variables prefixed with some identifier name for all your db parameters, this is really not ideal. You would typically deal with such info in ansible using a list or a dict (you still have a solution below if you really cannot change that)

This is an example variable declaration

# Example with a list
my_odbc_connection_list:
  - name: XXXX
    db_server: some.server.local
    db_name: acooldb
    username: someuser
    password: sosecret
    dsn: some dsn
  - name: YYYY
    db_server: other.server.local
    db_name: ahypedb
    username: otheruser
    password: verysecret
    dsn: other dsn

# Example with a dict
my_odbc_connection_dict:
  XXXX:
    db_server: some.server.local
    db_name: acooldb
    username: someuser
    password: sosecret
    dsn: some dsn
  YYYY:
    db_server: other.server.local
    db_name: ahypedb
    username: otheruser
    password: verysecret
    dsn: other dsn 

Using those structures, it become easy to go through all the connections to set with one single task and a loop. Again, here are two examples for each data structure above with the command used in your question

- name: Run odbc declaration commands from list
  win_command: powershell -executionpolicy unrestricted folder:\{{ item.name }}.ps1 -Server {{ item.db_server }} -UID {{ item.username }} -PWD {{ item.password }} -Database {{ item.db_name }} -DSN {{ item.dsn }}
  register: odbc_cmd
  loop: "{{ my_odbc_connection_list }}"

- name: show commands results
  debug:
    msg: "{{ odbc_cmd.results }}"

- name: Run odbc declaration commands from dict
  win_command: powershell -executionpolicy unrestricted folder:\{{ item.key }}.ps1 -Server {{ item.value.db_server }} -UID {{ item.value.username }} -PWD {{ item.value.password }} -Database {{ item.value.db_name }} -DSN {{ item.value.dsn }}
  register: odbc_cmd
  loop: "{{ my_odbc_connection_dict | dict2items }}"

- name: show commands results
  debug:
    msg: "{{ odbc_cmd.results }}"

From there, it is fairly easy to play only the relevant connection commands by passing a list of names. To provide it at runtime, the easiest way is to pass an --extra-var to the ansible-playbook command as a comma separated list of strings e.g.

ansible-playbook -i inventory odbc_play.yml --extra_var names_to_declare=XXXX,YYYY

You can provide a single element in the var. It will result in a single element list below.

You then only need to filter out all the elements which are not in that name list. This is basically equivalent to changing the loops with the following expressions:

# if using the data as list
loop: "{{ my_odbc_connection_list | selectattr('name', 'in', names_to_declare.split(',')) }}" 
# if using the data as dict
loop: "{{ my_odbc_connection_dict | dict2items | selectattr('key', 'in', names_to_declare.split(',')) }}"

Note that this will take care of filtering out typos in names as well if user entered one that does not exist.

There are other ways than "harcoding" that filter directly in the loop (declaring an other var, using set_fact, coping with defaults for names_to_declare if not provided - i.e. use all connections) but this is a bit beyond the question.


Now if you cannot cannot change your data structure it is still possible to cope with that. Meanwhile you will need a list containing all the known connection names (and you will have to update it if you add more connections) e.g.

known_odbc_connection_list:
  - XXXX
  - YYYY

This will enable you to loop over your existing vars being slightly more verbose:

- name: Run ocbc declaration commands
  vars:
    name: "{{ item }}"
    db_server: "{{ lookup('vars', item + '_DB_SERVER') }}"
    username: "{{ lookup('vars', item + '_Username') }}"
    password: "{{ lookup('vars', item + '_Password') }}"
    db_name: "{{ lookup('vars', item + '_DB_NAME') }}"
    dsn: "{{ lookup('vars', item + '_DSN') }}"
  win_command: powershell -executionpolicy unrestricted folder:\{{ name }}.ps1 -Server {{ db_server }} -UID {{ username }} -PWD {{ password }} -Database {{ db_name }} -DSN {{ dsn }}
  loop: "{{ known_odbc_connection_list }}"
  register: odbc_cmd

And using the same var as above at run time, you can modify your loop to filter connections as:

loop: "{{ known_odbc_connection_list | intersect(names_to_declare.split(',')) }}"

As with the 2 other solutions above, this latest expression will take care of filtering out non-existent names in case of a typo from the user.

Upvotes: 2

Related Questions