Monday, June 6, 2016

Ansible Variables



Ansible is great, I love it! But my first encounter with it was not pleasant, in fact, it was painful: I was seriously confused by its syntax, it looks so simple but it has so many small traps that can easily trip you, and when you fall, the error messages are sometimes mysterious. 

It took me a while to get used to the peculiarities of Ansible syntax, and in this blog, I will share my hard-learn lessons. 

All tests are conducted with Ansible:
vagrant@ctrl:/elkcloud/ansible$ ansible --version
ansible 2.0.2.0 (detached HEAD 7e05cc4e74) last updated 2016/04/25 18:29:09 (GMT +800)

Whitespaces

There are places that there must be whitespaces, and there are places that there must not be whitespaces. 

  #there mustn't be whitespaces surrounding = in set_fact
  - name: set a variable
    set_fact: v1="abc1"
    tags: [t1]
 
  #there mustn't be whitespaces surrounding = in debug
  #otherwise, Ansible will throw an error complaining about syntax 
  - debug: var=v1
    tags: [t1]   

  #if there are whitespaces surrounding = 
  #debug it will output:
  #"v2": "VARIABLE IS NOT DEFINED!" 
  - name: set a variable
    set_fact: v2 = "abc2"
    tags: [t1] 

  - debug: var=v2
    tags: [t1]
   
  #there must be whitespace after : in set_fact
  #otherwise Ansible will throw an error complaining about syntax  
  - name: set a variable
    set_fact:
      v3: "abc3"
      v4: "abc4"
tags: [t1]  

when to use {{variable }}

I used to be tripped by this big time, until I figured out this: {{ variable }} de-references the variable, meaning, {{ variable }} gets the variable value; you should use {{ variable }} when you set_fact, or concat a string; you should use variable in almost everywhere else. 

Debugging in Ansible typically uses debug task, be careful of its usage. debug var=variable, requires a variable, not a variable value, in other words, you shouldn’t use debug var={{variable}}. Here is a piece of code demonstrating this:

  - name: set var1
    set_fact:
      var1:  "abc"
    tags: [t2] 

  # "var1": "abc"
  - debug: var=var1
    tags: [t2] 

  #set_fact should assign a value to a varialbe
  - name: set var2
    set_fact:
      var2:  "{{var1}}"
    tags: [t2]     
     
  # "var2": "abc"   
  - debug: var=var2
    tags: [t2]

  # debug: var should use the variable, if use the value, debug will treat the value as a variable and try to dereference it
  #"abc": "VARIABLE IS NOT DEFINED!"
  - debug: var={{var2}}
    tags: [t2]   
   
  - name: set var3
    set_fact:
      var3:  var1
    tags: [t2]      
 
  #if set_fact assigns a variable to a variable, Ansible is not able to deference to get the value 
  #"var3": "var1"   
  - debug: var=var3 
    tags: [t2]
 
  #if set_fact assigns a variable to a variable, Ansible is not able to deference to get the value 
  # "msg": "var1" 
  - debug: msg={{var3}}
    tags: [t2]
 
  #if set_fact assigns a variable to a variable, Ansible is not able to deference to get the value 
  #stdout: var1 
  - name: check variable dereference
    shell: echo {{ var3 }}
tags: [t2]

Some part of this seems already counter-intuitive, when there are nested variables, some logic seems even more counter-intuitive. 

- name: set a nested var
    set_fact:
      nestedvars:
        dbs:   
          nodes:
            localhost: ["a","b","c"] 
    tags: [t3] 
   
  - name: set v
    set_fact:
      v: "dbs"
    tags: [t3]       

   #"nestedvars[v].nodes.localhost": [
   #     "a",
   #     "b",
   #     "c"
   # ]
  - debug: var=nestedvars.{{v}}.nodes.{{inventory_hostname}}
    tags: [t3]
   
   #"nestedvars[v].nodes.localhost": [
   #     "a",
   #     "b",
   #     "c"
   # ]
  - debug: var=nestedvars[v].nodes.{{inventory_hostname}}
    tags: [t3]

  # "nestedvars[v].nodes[inventory_hostname]": [
  #      "a",
  #      "b",
  #      "c"
  #  ] 
  - debug: var=nestedvars[v].nodes[inventory_hostname]
tags: [t3]

    #nestedvars.v.nodes.localhost": "VARIABLE IS NOT DEFINED!
  - debug: var=nestedvars.v.nodes.{{inventory_hostname}}
tags: [t3]   


So far so good, but when nested variables are used with with_items, the logic is not consistent:

    #nestedvars.dbs.nodes.localhost
  - name: echo item1
    shell:  echo {{item}}  
    with_items:  nestedvars.{{v}}.nodes.{{inventory_hostname}}
    tags: [t3]   
 
  #nestedvars[v].nodes.localhost
  - name: echo item2
    shell:  echo {{item}}  
    with_items:  nestedvars[v].nodes.{{inventory_hostname}}
    tags: [t3]
   
  #nestedvars.dbs.nodes[inventory_hostname]
  - name: echo item3
    shell:  echo {{item}}  
    with_items:  nestedvars.{{ v }}.nodes[inventory_hostname] 
    tags: [t3]
   
  #changed: [localhost] => (item=a)
  #changed: [localhost] => (item=b)
  #changed: [localhost] => (item=c)
  - name: echo item4
    shell:  echo {{item}}  
    with_items:  nestedvars[v].nodes[inventory_hostname]
tags: [t3] 

The rule of thumb seems to be: when use a nested variable with with_items, if the key is a variable, use [key_variable], not {{key_variable}}.This rule of thumb seems rather arbitrary, but remembering it saves me a lot of confusion.



One more arbitrary rule of them is: delegate_to must be used with a value, not a variable.

No comments:

Post a Comment