6
votes

I have two servers in my inventory (hosts)

[server]
10.23.12.33
10.23.12.40

and playbook (play.yml)

---
- hosts: all
  roles:
    web

Inside web role in vars directory i have main.yml

---
file_number : 0

Inside web role in tasks directory i have main.yml

---
- name: Increment variable
  set_fact: file_number={{ file_number | int + 1 }}

- name: create file
  command: 'touch file{{ file_number }}'

Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1

So this variable is local for every machine, how could i make it global for all machines.

My file structure is:

hosts
play.yml
roles/
  web/
    tasks/
      main.yml
    vars/
      main.yml
3
It is a global variable but Ansible is only evaluating file_number once on the set_fact task and so it only gets set to 1 no matter how many nodes you run it against. Can you explain your use case slightly broader? It could be that there is a better way of doing what you're trying to do (it seems you want a unique filename across all hosts)? - ydaetskcoR
@ydaetskcoR what i am exactly doing is changing configuration line in file which in first machine it will have 'username=client1' and in second machine it will have 'username=client2' and so on for all hosts. therefore i need to increment the variable for every machine. - Nasr
But you have no idea what order of hosts the task will run in. Each run will likely have a different order of hosts. You may be better to assign this as a host var instead unless I'm missing something about your use case - ydaetskcoR

3 Answers

4
votes

I haven't found a solution with ansible although i made work around using shell to make global variable for all hosts.

  1. Create temporary file in /tmp in localhost and place in it the starting count
  2. Read the file for every host and increment the number inside the file

I created the file and initialized it in the playbook (play.yml)

- name: Manage localhost working area
  hosts: 127.0.0.1
  connection: local
  tasks:
    - name: Create localhost tmp file
      file: path={{ item.path }} state={{ item.state }}
      with_items:
       - { path: '/tmp/file_num', state: 'absent' }
       - { path: '/tmp/file_num', state: 'touch' }

    - name: Managing tmp files
      lineinfile: dest=/tmp/file_num line='0'

Then in web role in main.tml task i read the file and increment it.

- name: Get file number
  local_action: shell file=$((`cat /tmp/file_num` + 1)); echo $file | tee /tmp/file_num
  register: file_num

- name: Set file name
  command: 'touch file{{ file_num.stdout }}'

Now i have in first host file1 and in second host file2

3
votes

Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1

You need to keep in mind that variables in Ansible aren't global. Variables (aka 'facts') are applied uniquely to each host, so file_number for host1 is different than file_number for host2. Here's an example based loosely on what you posted:

roles/test/vars/main.yml:

---
file_number: 0

roles/test/tasks/main.yml:

---
- name: Increment variable
  set_fact: file_number={{ file_number | int + 1 }}

- name: debug
  debug: msg="file_number is {{ file_number }} on host {{ inventory_hostname }}"

Now suppose you have just two hosts defined, and you run this role multiple times in a playbook that looks like this:

---
- hosts: all
  roles:
    - { role: test }

- hosts: host1
  roles:
    - { role: test }

- hosts: all
  roles:
    - { role: test }

So in the first play the role is applied to both host1 & host2. In the second play it's only run against host1, and in the third play it's again run against both host1 & host2. The output of this playbook is:

PLAY [all] ********************************************************************

TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]

TASK: [test | debug] **********************************************************
ok: [host1] => {
    "msg": "file_number is 1 on host host1"
}
ok: [host2] => {
    "msg": "file_number is 1 on host host2"
}

PLAY [host1] **************************************************

TASK: [test | Increment variable] *********************************************
ok: [host1]

TASK: [test | debug] **********************************************************
ok: [host1] => {
    "msg": "file_number is 2 on host host1"
}

PLAY [all] ********************************************************************

TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]

TASK: [test | debug] **********************************************************
ok: [host1] => {
    "msg": "file_number is 3 on host host1"
}
ok: [host2] => {
    "msg": "file_number is 2 on host host2"
}

So as you can see, the value of file_number is different for host1 and host2 since the role that increments the value ran against host1 more times than it did host2.

Unfortunately there really isn't a clean way making a variable global within Ansible. The entire nature of Ansible's ability to run tasks in parallel against large numbers of hosts makes something like this very tricky. Unless you're extremely careful with global variables in a parallel environment you can easily trigger a race condition, which will likely result in unpredictable (inconsistent) results.

1
votes

You can use Matt Martz's solution from here.

Basically your task would be like:

- name: Set file name
command: 'touch file{{ play_hosts.index(inventory_hostname) }}'

And you can remove all that code for maintaining global var and external file.