I am new to ansible and am trying to use variables from a vars.yml file in a playbook.yml file.
vars.yml
---
- firstvar:
id: 1
name: One
- secondvar:
id: 2
name: two
playbook.yml
---
- hosts: localhost
tasks:
- name: Import vars
include_vars:
file: ./vars.yml
name: vardata
- name: Use FirstVar
iso_vlan:
vlan_id: "{{ vardata.firstvar.id }}"
name: "{{ vardata.firstvar.name }}"
state: present
- name: Use Secondvar
iso_vlan:
vlan_id: "{{ vardata.secondvar.id }}"
name: "{{ vardata.secondvar.name }}"
state: present
So you can see here I am treating the imported variable data, which is stored in vardata, as object and trying to call each of them in other tasks. I am pretty sure these imported vars at the first task are only available in that very task. How can I use that in other tasks? It would output as variables undefined for each tasks. Any input is appreciated.
Your vars.yml file isn't formatted correctly.
Try this:
---
firstvar:
id: 1
name: One
secondvar:
id: 2
name: two
I used this to test it:
---
- hosts: localhost
tasks:
- name: Import vars
include_vars:
file: ./vars.yml
name: vardata
- name: debug
debug:
msg: "{{ vardata.firstvar.name }}"
- name: more debug
debug:
msg: "{{ vardata.secondvar.id }}"
On top of the error you made when declaring the variables (syntax is very important), you can also define include_vars: ./vars.yml such that you can just call {{ firstvar.name }}, {{ firstvar.id }} immediately. Much more leaner/shorter.
Related
I'm just a beginner and write some small playbook.
For now I get some little error.
- name: Facts
ios_command:
commands: show interface status
register: ios_comm_result
In this playbook, for beginner I get the list of interfaces and then in next task I get list with first position including column name.
- name: get a list of ports to modify
set_fact:
newlist: "{{ (newlist | default([])) + [item.split()[0]] }}"
with_items: "{{ ios_comm_result.stdout_lines }}"
Output:
'Port'
'fa0/1'
'gi0/2'
....
etc
How can I delete first line? Or where can I read about this?
Based on your requirement and the given comments about Accessing list elements with Slice notation ([1:]) and Jinja Filters (| reject()) I've setup an short test
---
- hosts: localhost
become: false
gather_facts: false
vars:
STDOUT_LINES: ['Port', 'fa0/1', 'gi0/2', 'etc/3']
tasks:
# Slice notation
- name: Show 'stdout_lines' from the 2nd line to the end
debug:
msg: "{{ item }}"
loop: "{{ STDOUT_LINES[1:] }}"
# Jinja2 filter
- name: Show 'stdout_lines' and drop any lines which match
debug:
msg: "{{ item }}"
loop: "{{ STDOUT_LINES | reject('match', '^Port') | list }}"
# Conditionals
- name: Show 'stdout_lines' but not the first one
debug:
msg: "{{ item }}"
when: ansible_loop.index != 1
loop: "{{ STDOUT_LINES }}"
loop_control:
extended: true
label: "{{ ansible_loop.index0 }}"
with some value added about Extended loop variables and using conditionals in loops.
All three options show the expected result.
I have a two main variable with subvariables. The main variable VELABx and VELABy match the ansible_hostnames. Following an example of the variables mentioned:
VELABx
location: "text1.1"
initialism: "text1.2"
VELABy
location: "text2.1"
initialism: "text2.2"
Now I'd like to run a task which replaces a regexp with the location variable based on the current ansible_hostname. So, I tried this code, but it leads into an error.
- name: Replace location for VELABx
replace:
dest: "/path/to/file"
regexp: 'location'
replace: "{{ {{ansible_hostname}}.location }}"
remote_user: rssuser
become: no
when: ansible_hostname == "VELABx"
What am I doing wrong? How can I solve this? Can anyone bring up a solution?
Things are always easier when you manage to create a proper structure of data. For example, given the dictionaries in a file
shell> cat velab.yml
VELABx:
location: "text1.1"
initialism: "text1.2"
VELABy:
location: "text2.1"
initialism: "text2.2"
you can include the data from the file into a dictionary, e.g.
- hosts: VELABx,VELABy,VELABz
tasks:
- include_vars:
file: velab.yml
name: velab
run_once: true
- debug:
var: velab
run_once: true
gives
velab:
VELABx:
initialism: text1.2
location: text1.1
VELABy:
initialism: text2.2
location: text2.1
The data you provided in the question are not YAML dictionaries. The colon is missing behind the keys. Either fix it, if you can or parse the file. Anyway, create the dictionary whatever is the source.
Then, given the files
shell> ssh admin#10.1.0.61 cat /tmp/velab.txt
location
shell> ssh admin#10.1.0.62 cat /tmp/velab.txt
location
shell> ssh admin#10.1.0.63 cat /tmp/velab.txt
location
the task to replace the location is simple
- name: Replace location for VELAB*
replace:
dest: /tmp/velab.txt
regexp: location
replace: "{{ velab[inventory_hostname]['location'] }}"
when: velab[inventory_hostname] is defined
gives
shell> ansible-playbook playbook.yml -CD
PLAY [VELABx,VELABy,VELABz] ******************************************
...
TASK [Replace location for VELAB*] ***********************************
skipping: [VELABz]
--- before: /tmp/velab.txt
+++ after: /tmp/velab.txt
## -1 +1 ##
-location
+text1.1
changed: [VELABx]
--- before: /tmp/velab.txt
+++ after: /tmp/velab.txt
## -1 +1 ##
-location
+text2.1
changed: [VELABy]
The next option is to remove the condition and use the default value, instead of skipping the host, when the host is missing in the dictionary
- name: Replace location for VELAB*
replace:
dest: /tmp/velab.txt
regexp: location
replace: "{{ velab[inventory_hostname]['location']|
default(default_location) }}"
vars:
default_location: "text99.1"
you must use loopkup, {{}} inside other {{}} is incorrect
---
- hosts: localhost
gather_facts: false
vars:
ansible_hostname: VELABx
VELABx:
location: "text1.1"
initialism: "text1.2"
VELABy:
location: "text2.1"
initialism: "text2.2"
tasks:
- name: DEBUG
debug:
msg: "{{ lookup('vars', ansible_hostname ).location }}"
- name: Replace location for VELABx
replace:
dest: "/path/to/file"
regexp: 'location'
replace: "{{ lookup('vars', ansible_hostname ).location }}"
when: ansible_hostname == "VELABx"
I tried this one
---
- name: py
hosts: master
tasks:
- name:
command: /home/vagrant/test.py
register: csvfile
changed_when: false
- debug:
var: csvfile
- name: Create csvfile directories
file:
path: "/tmp/{{ item.host }}_{{ item.app }}"
state: directory
with_dict: "{{ csvfile }}"
Test.py results are:
{'key': 'stdout_lines', 'value': ["{'host': '123', 'app': 'abc'}", "{'host': '2345', 'app': 'def'}", "{'host': '8484', 'app': 'ghju'}", "{'host': '89393', 'app': 'yruru'}"]}
and i'm getting error at "/{{ item.host }}_{{ item.app }}"
Can someone help me?
The registered variable csvfile must have the attribute stdout_lines
csvfile:
stdout_lines:
- {'host': '123', 'app': 'abc'}
- {'host': '2345', 'app': 'def'}
- {'host': '8484', 'app': 'ghju'}
- {'host': '89393', 'app': 'yruru'}
The simple loop should do the job
- name: Create csvfile directories
file:
path: "/tmp/{{ item.host }}_{{ item.app }}"
state: directory
loop: "{{ csvfile.stdout_lines }}"
The key/value decomposition was added very probably by with_dict. Please confirm, update and fix the question. Post the output of the task
- debug:
var: csvfile
There are a bunch of things wrong, so while this is an "answer" because it is too big to fit into a comment here on S.O., it is not the fix to your problem because there is so much wrong with your code.
As your debug output demonstrates, the register: captures the output of the whole task, and not just the output from your program. Thus, at the very least you would need with_dict: "{{ csvfile.stdout }}" but that, too, will not work because the output is not an interoperability format that ansible can use. Just because it is written in python and your script is written in python does not mean they can communicate
You will need to have the test.py call json.dump or json.dumps on the results, and not just print() or repr or whatever it is calling now, so that the output can be parsed by ansible back into an actual data structure in your playbook
Then, what happens next depends on whether you continue to write out each dictionary from test.py on a per-line basis, or you choose to package them all into a list of dictionaries and dump that as JSON
Start by fixing the output from test.py to be parseable by ansible, and we'll go from there
Sumanth, you can try the same approach but using with_items:
- name: Create csvfile directories
file:
path: "/tmp/{{ item.host }}_{{ item.app }}"
state: directory
with_items: "{{ csvfile.stdout_lines }}"
I am using Ansible to read in a YAML file containing a list of dictionaries. I then need to iterate through this list and call a different role depending on data within each list object. I have a solution that is working, but it seems so kludgy to me, that I wanted to find out if there was a better way.
I have defined a YAML file structure to provide the input to my playbook and I read that file into a variable with the include_vars module. I then use with_items to loop through the array one time for each role that I need to support (currently 6, but it may increase) and use a when clause to only include_role when the data in the object is correct for that role.
Code
Sample Input file:
---
objects:
- type: type1
name: obj_type1
- type: type2
name: obj_type2
- type: type3
name: obj_type3
Sample Playbook:
---
- hosts: cf-host
gather_facts: False
vars:
objects_file: ''
tasks:
- name: Read objects_file
include_vars:
file: "{{ objects_file }}"
- name: Handle type1
include_role:
name: type1_role
vars:
- type1_role_name: "{{ item.name }}"
with_items: "{{ objects }}"
when: item.type == "type1"
- name: Handle type2
include_role:
name: type2_role
vars:
- type2_role_name: "{{ item.name }}"
with_items: "{{ objects }}"
when: item.type == "type2"
- name: Handle type3
include_role:
name: type3_role
vars:
- type3_role_name: "{{ item.name }}"
with_items: "{{ objects }}"
when: item.type == "type3"
Sample Role:
---
- name: Print name
debug:
msg: "Type 1 - Name is {{ type1_role_name }}"
The other roles are the same, but have msg set to "Type X - Name is {{ typeX_role_name }}" instead.
This leads to a skipped task for every item in the list when it does not match the type corresponding to the role. It also means that I have to loop through the same list multiple times (as many as types that I need to support). As mentioned, I already need to support 6 different types and that number may grow.
This solution seems like it would scale very poorly and would have terrible performance as the list and supported types become larger and larger. Is there a better way to do what I need to do?
Better Solution
Using this answer: https://stackoverflow.com/a/53859851/9744341 as a base, I was able to come up with this as a better solution that I am happy with. Here it is in case anyone else has the same needs:
Sample Playbook:
---
- hosts: cf-host
gather_facts: False
vars:
objects_file: ''
role_name_lookup:
type1:
role_name: type1_role
tasks_from: type1_tasks
type2:
role_name: type2_role
tasks_from: type2_tasks
type3:
role_name: type3_role
tasks_from: main
tasks:
- name: Read objects_file
include_vars:
file: "{{ objects_file }}"
- name: Call roles to create infra
include_role:
name: "{{ role_name_lookup[item.type].role_name }}"
tasks_from: "{{ role_name_lookup[item.type].tasks_from }}"
vars:
inputs: "{{ item }}"
with_items: "{{ objects }}"
Sample Role:
---
- name: Print name
debug:
msg: "Type 1 - Name is {{ inputs.name }}"
The other roles are the same, but have msg set to Type X - Name is {{ inputs.name }} instead.
Would this code fit your purpose?
- name: Include roles
include_role:
name: "{{ item.type }}_role"
with_items: "{{ objects }}"
- name: Print name
debug:
msg: "{{ item.type }} - Name is {{ role_name }}"
My requirement is that I want to dynamically include a variable file in my Ansible script. I can do that by putting following in my ansible task-
- name: Include vars file
include_vars: vars/dev.yml
- name: Some other task
cp: copy something
Above works if I keep the dev.yml in my vars directory. Now I actually do not want to put the dev.yml in the directory, I want to pull it from S3 and then use the variable in it. Something like below-
- name: Get dev file
s3:
bucket: bucket_name
object: object_name
dest: "dest_directory" ## Here I want the destination to be vars/dev.yml
mode: get
aws_access_key: "{{ s3.aws_access_key }}"
aws_access_key: "{{ s3.aws_secret_key }}"
- name: Include vars file
include_vars: vars/dev.yml
- name: Some other task that uses vars in dev.yml
template: render some template using vars in dev.yml and copy to server
The above will actually not work. How do I do this?
Thanks Konstantin Suvorov for help. I just needed to add delegate_to in my task.
- name: Get dev file
s3:
bucket: bucket_name
object: object_name
dest: vars/dev.yml
mode: get
aws_access_key: "{{ s3.aws_access_key }}"
aws_access_key: "{{ s3.aws_secret_key }}"
delegate_to: localhost
- name: Include vars file
include_vars: vars/dev.yml