简体   繁体   中英

Ansible - how to conditionally invert variables in a playbook

I needed to be able to invert variables stored in a JSON file that is passed to the playbook from the command line.

These are the tasks that I set up (they are identical except for vars), this is a fragment of a playbook:

- name: Prepare a .sql file
  delegate_to: 127.0.0.1
  mysql_db:
    name: "{{ source['database']['db_name'] }}"
    state: dump
    login_host: "{{ source['database']['host'] }}"
    login_user: "{{ source['database']['user'] }}"
    login_password: "{{ source['database']['password'] }}"
    target: test_db.sql
  when: invert is not defined

- name: Prepare a .sql file (inverted)
  delegate_to: 127.0.0.1
  mysql_db:
    name: "{{ target['database']['db_name'] }}"
    state: dump
    login_host: "{{ target['database']['host'] }}"
    login_user: "{{ target['database']['user'] }}"
    login_password: "{{ target['database']['password'] }}"
    target: test_db.sql
  when: invert is defined

So consequently when I execute

ansible-playbook -i hosts playbook.yml --extra-vars "@dynamic_vars.json"

the first task is executed. If I execute

ansible-playbook -i hosts playbook.yml --extra-vars "@dynamic_vars.json" --extra-vars "invert-yes"

the second task is executed that takes the same hash as parameters, but only swaps source for target (which essentially becomes a source in my playbook).

As you can see, this is a very simplistic approach, there is a lot of unnecessary duplication, I just do not like it. However, I cannot think of a better way to be able to revert variables at the command line without building some more complex include logic.

Perhaps you can advice me on how I can do it better? Thanks!

I'm a big fan of YAMLs anchors and references when it comes to the topic of avoiding repetition. Since the content is dynamic, you could take advantage of with_items , which can be used to pass a parameter like so:

- &sqldump
  name: Prepare a .sql file
  delegate_to: 127.0.0.1
  mysql_db:
    name: "{{ item['database']['db_name'] }}"
    state: dump
    login_host: "{{ item['database']['host'] }}"
    login_user: "{{ item['database']['user'] }}"
    login_password: "{{ item['database']['password'] }}"
    target: test_db.sql
  when: invert is not defined
  with_items:
    - source


- <<: *sqldump
  name: Prepare a .sql file (inverted)
  when: invert is defined
  with_items:
    - target

The 2nd task is a perfect clone of the first one, you then override the name , condition and the loop with_items to pass the target instead of the source .


After reading your answer to @ydaetskcoR it sounds like you have quite some cases where you need to use the data from one or the other dict. Maybe in that case it then would make sense to just define the var globally depending on the invert parameter. Your vars file could look like that:

---

source:
  database: ...
  db_name: ...

target:
  database: ...
  db_name: ...

data: "{{ target if invert is defined else source }}"

You then simply can use data in all your tasks without dealing with conditions any further.

- name: Prepare a .sql file
  delegate_to: 127.0.0.1
  mysql_db:
    name: "{{ data['database']['db_name'] }}"
    state: dump
    login_host: "{{ data['database']['host'] }}"
    login_user: "{{ data['database']['user'] }}"
    login_password: "{{ data['database']['password'] }}"
    target: test_db.sql

Of course, this way you have a fixed task name which does not change with the param you pass.

If you are attempting to do the same thing but just want to specify different variables depending on the host/group then a better approach may be to simply set these as host/group vars and run it as a single task.

If we set up our inventory file a bit like this:

[source_and_target-nodes:children]
source-nodes
target-nodes

[source-nodes]
source database_name='source_db' database_login_user='source_user' database_login_pass='source_pass'

[target-nodes]
target database_name='target_db' database_login_user='target_user' database_login_pass='target_pass'

Then we can target the task at the source_and_target-nodes like so:

- name: Prepare a .sql file
  hosts: source_and_target-nodes
  mysql_db:
    name: "{{ database_name }}"
    state: dump
    login_host: "{{ inventory_hostname }}"
    login_user: "{{ database_login_user }}"
    login_password: "{{ database_login_pass }}"
    target: test_db.sql

You won't be able to access the host vars of a different host this easily if you need to use delegate_to as you are in your question but if you are simply needing to run the play locally you can instead set ansible_connection to local in your host/group vars or setting connection: local in the play.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM