Search code examples
ansibleansible-inventoryansible-template

How to use a variable defined in a previous task for use in a task where the conditional omits that host?


Effectively, I have two servers, and I am trying to get use output from the command of one of them, to configure the other, and vise versa. I spent a few hours reading on this, and found out that the hostvars process and dummy hosts is seemingly what I want. No matter how I try to implement this process, I still get undefined variables, and/or failures from the host(s) not being in the pattern for the task:

Here is the relevant block, with only hosts mux-ds1 and mux-ds2 are in the dispatchers group:

---
 - name: Play that sets up the sql database during the build process on all mux dispatchers.
   hosts: mux_dispatchers
   remote_user: ansible
   vars:
     ansible_ssh_pipelining: yes

   tasks:

     - name: Check and save database master bin log file and position on mux-ds2.
       shell: sudo /usr/bin/mysql mysql -e "show master status \G" | grep -E 'File:|Position:' | cut -d{{':'}} -f2 | awk '{print $1}'
       become: yes
       become_method: sudo
       register: syncds2
       when: ( inventory_hostname == 'mux-ds2' )

     - name: Print current ds2 database master bin log file.
       debug:
         var: "syncds2.stdout_lines[0]"

     - name: Print current ds2 database master bin position.
       debug:
         var: "syncds2.stdout_lines[1]"

     - name: Add mux-ds2 some variables to a dummy host allowing us to use these variables on mux-ds1.
       add_host:
         name: "ds2_bin"
         bin_20: "{{ syncds2.stdout_lines }}"

     - debug:
         var: "{{ hostvars['ds2_bin']['bin_21'] }}"

     - name: Compare master bin variable output for ds1's database and if different, configure for it.
       shell: sudo /usr/bin/mysql mysql -e "stop slave; change master to master_log_file='"{{ hostvars['ds2_bin']['bin_21'][0] }}"', master_log_pos="{{ hostvars['ds2_bin']['bin_21'][1] }}"; start slave"
       become: yes
       become_method: sudo
       register: syncds1
       when: ( inventory_hostname == 'mux-ds1' )

Basically everything works properly up to where I try to see the value of the variable from the dummy host with the debug module, but it tells me the variable is still undefined even though it's defined in the original variable. This is supposed to be the system to get around such problems:

TASK [Print current ds2 database master bin log file.] **************************************************
ok: [mux-ds1] => {
    "syncds2.stdout_lines[0]": "VARIABLE IS NOT DEFINED!"
}
ok: [mux-ds2] => {
    "syncds2.stdout_lines[0]": "mysql-bin.000001"
}

TASK [Print current ds2 database master bin position.] **************************************************
ok: [mux-ds1] => {
    "syncds2.stdout_lines[1]": "VARIABLE IS NOT DEFINED!"
}
ok: [mux-ds2] => {
    "syncds2.stdout_lines[1]": "107"
}

The above works as I intend, and has the variables populated and referenced properly for mux-ds2.

TASK [Add mux-ds2 some variables to a dummy host allowing us to use these variables on mux-ds1.] ********
fatal: [mux-ds1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout_lines'\n\nThe error appears to be in '/home/ansible/ssn-project/playbooks/i_mux-sql-config.yml': line 143, column 8, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n     - name: Add mux-ds2 some variables to a dummy host allowing us to use these variables on mux-ds1.\n       ^ here\n"}

This is where the issue is, the variable seems to be magically undefined again, which is odd given this process is designed to end-run that issue. I can't even make it to the second set of debug tasks.

Note that this is ultimately for the purpose of syncing up two master/master replication mysql databases. I'm also doing this with the shell module because the mysql version which must be used can be no higher than 5.8, and the ansible module requires 5.9, which is a shame. There same process will be done for mux-ds2 in reverse as well assuming this can be made to work.

Either I'm making a mistake in this implementation which keeps it from functioning, or I'm using the wrong implementation for what I want. I've spent too much time now trying to figure this out alone and would appreciate any solution for this which would work. Thanks in advance!


Solution

  • Seems like you are going a complicated route when a simple delegation of tasks and the usage of the special variable hostvars, to be able to fetch fact from different node, should give you what you expect.

    Here is an example — focusing just on the important part — so, you might want to add the become and become_user back in there:

    - shell: >-
        sudo /usr/bin/mysql mysql -e "show master status \G" 
        | grep -E 'File:|Position:' 
        | cut -d{{':'}} -f2 
        | awk '{print $1}'
      delegate_to: mux-ds2
      run_once: true
      register: syncds2
    
    - shell: >-
        sudo /usr/bin/mysql mysql -e "stop slave; 
          change master to 
          master_log_file='"{{ hostvars['mux-ds2'].syncds2.stdout_lines.0 }}"', 
          master_log_pos="{{ hostvars['mux-ds2'].syncds2.stdout_lines.1 }}"; 
          start slave"
      delegate_to: mux-ds1
      run_once: true
    

    Here is an example, running some dummy shell tasks, given the playbook:

    - hosts: node1, node2
      gather_facts: no
    
      tasks:
        - shell: |
            echo 'line 0'
            echo 'line 1'
          delegate_to: node2
          run_once: true
          register: master_config
    
        - shell: |
            echo '{{ hostvars.node2.master_config.stdout_lines.0 }}'
            echo '{{ hostvars.node2.master_config.stdout_lines.1 }}'
          delegate_to: node1
          run_once: true
          register: master_replicate_config
    
        - debug:
            var: master_replicate_config.stdout_lines
          delegate_to: node1
          run_once: true
    

    This would yield:

    PLAY [node1, node2] **********************************************************
    
    TASK [shell] *****************************************************************
    changed: [node1 -> node2(None)]
    
    TASK [shell] *****************************************************************
    changed: [node1]
    
    TASK [debug] *****************************************************************
    ok: [node1] => 
      master_replicate_config.stdout_lines:
      - line 0
      - line 1