How to read an Ansible variable from a string containing YAML? - ansible

I have a task which runs an uri call to get a JSON file containing a YAML fragment (it is obtained from Rancher API). I'm able to extract the YAML fragment using the following task
- name: generate_stack_call_body.yml read docker compose from catalog
set_fact:
docker_compose: '{{ template_detail|json_query(dc_query) }}'
When I run the ansible debug strategy, it indicates my docker_compose variable contains
(debug) p vars['docker_compose']
u"consul:\n labels:\n io.rancher.container.hostname_override: container_name\n io.rancher.container.pull_image: always\n io.rancher.container.hostname_override: container_name\n io.rancher.scheduler.global: 'true'\n stdin_open: true\n image: registry.mycompany.com/my-project/consul-rancher:0.9.0\n volumes:\n - /data/consul:/consul/data\nload-balancer:\n ports:\n - 8500:8500\n labels:\n io.rancher.container.hostname_override: container_name\n io.rancher.scheduler.global: 'true'\n stdin_open: true\n image: rancher/load-balancer-service\n links:\n
- consul:consul\n"
Which looks like valid YAML to me.
As a consequence, I guess it should be possible to have that value interpreted as a valid Ansible variable.
But how can I have it interpreted as a variable?
And how can I later put back that variable in a string?

But how can I have it interpreted as a variable?
set_fact:
docker_compose: '{{ template_detail | json_query(dc_query) | from_yaml }}'
And how can I later put back that variable in a string?
{{ docker_compose | to_yaml }}
Reference

Related

Ansible - set variable based on other item in list

I have configured this list as a variable:
my_list:
- name: whatever
url: http://example.com
- name: dummy
url: http://example.com
Can I configure a variable based on a name in my_list? Something I've tried, but fails because the variable doesn't loop over the list:
other_list:
- a_name: "{{ ip_list.name if my_list.name == 'whatever' }}"
a_url: "{{ ip_list.url if my_list.name == 'whatever' }}"
The expected outcome would be:
other_list:
- a_name: whatever
a_url: http://example.com
I don't want to run Ansible to set the variable during runtime, but I'd rather configure these variable in the variable files.

Ansible variable usage. Jinja2 warning about delimiter usage

I know this looks like a duplicate question about this particular warning, however my use case is using the variable in a way that interprets as a literal string when the {{ brackets }} are removed
ansible 2.8.1
I have the following WORKING Ansible snippet, note the when clause:
- name: Set DNS for data host 1
route53:
command: create
zone: "{{ dns_domain_name }}"
record: "elasticsearch-{{ env }}-01"
type: A
ttl: 300
value: "{{ ec2_private_ip_address }}"
wait: no
private_zone: True
overwrite: yes
when: tag_name is match("xhost-{{ env }}01")
I'm getting the following warning:
[WARNING]: conditional statements should not include jinja2 templating
delimiters such as {{ }} or {% %}. Found: tag_name is
match("xhost-{{ env }}01")
If I remove the delimeter:
when: tag_name is match("xhost-env01")
Won't work since it's part of the match() query string. I get no errors but the match query doesn't work.
How can I handle this without getting the annoying warnings?
Thanks!
You can create an additional variable and then use the variable in match().
- name: Set DNS for data host 1
route53:
command: create
zone: "{{ dns_domain_name }}"
record: "elasticsearch-{{ env }}-01"
type: A
ttl: 300
value: "{{ ec2_private_ip_address }}"
wait: no
private_zone: True
overwrite: yes
when: tag_name is match(matcher)
vars:
matcher: "xhost-{{ env }}01"

How to use a dictionary of registered ansible variables in vars?

I want to pass multiple variables to a task using vars. Currently, I am doing it like below
vars:
var1_name: "var1_value"
var2_name: "var2_value"
As the number of variables can grow in size, I'd rather prefer to pass the dictionary of variables to the task using vars. I have constructed a dictionary of variables like below
- name: set fact
hosts: localhost
tasks:
- set_fact:
variables: "{{ variables|default({}) | combine( {item.variable: item.value} ) }}"
with_items:
- variable: var1_name
value: "var1_value"
- variable: var2_name
value: "var2_name"
Dictionary looks something like this:
"variables": {
"var1_name": "var1_value",
"var2_name": "var2_value",
}
Now, I want to make variables in this dictionary available to roles executing on other hosts.
But, when I tried to pass dictionary to vars like below
vars: "{{ variables }}"
Ansible throws the error:
ERROR! Vars in a Play must be specified as a dictionary, or a list of dictionaries
How to pass a dictionary variable in vars?
After doing some searching through the Ansible source code, it looks like this is an issue even the developers of Ansible face. In some integration tests, there are specific tests that are commented out because of this same error.
https://github.com/ansible/ansible/blob/devel/test/integration/targets/include_import/role/test_include_role.yml#L96
## FIXME Currently failing with
## ERROR! Vars in a IncludeRole must be specified as a dictionary, or a list of dictionaries
# - name: Pass all variables in a variable to role
# include_role:
# name: role1
# tasks_from: vartest.yml
# vars: "{{ role_vars }}"
I also found out that this is the underlying function that is being called to include the variables:
https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/base.py#L681
def _load_vars(self, attr, ds):
'''
Vars in a play can be specified either as a dictionary directly, or
as a list of dictionaries. If the later, this method will turn the
list into a single dictionary.
'''
def _validate_variable_keys(ds):
for key in ds:
if not isidentifier(key):
raise TypeError("'%s' is not a valid variable name" % key)
try:
if isinstance(ds, dict):
_validate_variable_keys(ds)
return combine_vars(self.vars, ds)
elif isinstance(ds, list):
all_vars = self.vars
for item in ds:
if not isinstance(item, dict):
raise ValueError
_validate_variable_keys(item)
all_vars = combine_vars(all_vars, item)
return all_vars
elif ds is None:
return {}
else:
raise ValueError
except ValueError as e:
raise AnsibleParserError("Vars in a %s must be specified as a dictionary, or a list of dictionaries" % self.__class__.__name__,
obj=ds, orig_exc=e)
except TypeError as e:
raise AnsibleParserError("Invalid variable name in vars specified for %s: %s" % (self.__class__.__name__, e), obj=ds, orig_exc=e)
Seems as if since "{{ }}" is actually just a YAML string, Ansible doesn't recognize it as a dict, meaning that the vars attribute isn't being passed through the Jinja2 engine but instead being evaluated for what it actually is.
The only way to pass YAML objects around would be to use anchors, however this would require that the object be defined in whole instead of dynamically.
var: &_anchored_var
attr1: "test"
att2: "bar"
vars:
<<: *_anchored_var
I recommend using a structured way of managing variables like:
File myvars1.yml
myvars:
var1_name: "var1_value"
var2_name: "var2_value"
Then read the variables like
- name: Read all variables
block:
- name: Get All Variables
stat:
path: "{{item}}"
with_fileglob:
- "/myansiblehome/vars/common/myvars1.yml"
- "/myansiblehome/vars/common/myvars2.yml"
register: _variables_stat
- name: Include Variables when found
include_vars : "{{item.stat.path}}"
when : item.stat.exists
with_items : "{{_variables_stat.results}}"
no_log : true
delegate_to: localhost
become: false
After that, use like:
- name: My Running Module
mymodule:
myaction1: "{{ myvars.var1_name }}"
myaction2: "{{ myvars.var2_name }}"
Hope it helps

Ansible conditional template variable substitution

I am trying to create configuration files from a template with include variables based on the fourth character of {{ ansible_hostname }}.
What works:
playbook:
---
- hosts: spock
roles:
- templaterole
role:
---
- name: testing autofs template on spock
template:
src=autofs
dest=/tmp/autofs
with_items:
- "{{ var_a }}"
when: ('{{ ansible_hostname }}' == "spock")
vars/main.yml:
var_a:
-
var_1: 'this is var_a1'
var_2: 'this is var_a2'
var_b:
-
var_1: 'this is var_b1'
var_2: 'this is var_b2'
template:
{{ item.var_1 }}
#
{{ item.var_2 }}
#
This works as expected and the output produces a /tmp/autofs file on the spock host that looks like:
this is var_a1
#
this is var_a2
#
Now, if I try to write the file based on trying to pull out the 4th character of the {{ ansible_hostname }}, the play does not get a match on the conditional and does not write the file. I'm trying this conditional in my role:
---
- name: testing autofs template on spock
template:
src=autofs
dest=/tmp/autofs
with_items:
- "{{ var_a }}"
when: ('{{ ansible_hostname }} | cut -c4' == "c") or
('{{ ansible_hostname }} | cut -c4' == "k")
the play skips this task due to not matching on the conditional. Ultimately i want to be able to pull any 4th character of our hostnames as this will always be predictable (can only be one of 4 known characters which defines my environment and lets me define the correct template variables based on these diff production environments.)
Can anyone help me to redefine my when statement such that i can do or conditionals and pull characters out of defined ansible variables like ansible_hostname?
Don't use curly brackets inside when statement, it's already a Jinja2 statement.
And in Jinja2 statements you use | to apply filter, but there is no cut filter available.
Your statement should be as simple as:
when: ansible_hostname[3] in 'ck'

How to concatenate string in YAML?

I am writing a playbook in which I need to execute mysql query and pass
output in json format. playbook code working fine just I want facing error in string concatenate part. If I am passing sample json string it is working fine.
- name: Execute php script with json parameter
command: php -f /path/to/php/test.php "{'colors':{'red','blue','yellow'}}"
register: php_output
output.stdout_lines is variable already set in my playbook which contains output in {'red','blue','yellow'} format.
- name: Execute php script with json parameter
command: php -f /path/to/php/test.php '{"stdout_lines": {{output.stdout_lines}} }'
register: php_output
So how can I concatenate output.stdout_lines variable in '{"stdout_lines": {{output.stdout_lines}} }' ? any suggestions
stdout_lines was created for convenience. Back in the day we only had stdout. Which is what you want, I think:
command: php -f /path/to/php/test.php '{"stdout_lines": {{output.stdout}} }'
and if you want to really concat yourself, say because you have your own list of strings then you can use the Jinja2 built-in filter join:
- hosts: localhost
gather_facts: False
tags: so9
vars:
- str_list: ['Hello', 'world', '!', ]
tasks:
- debug: msg="orig list={{ str_list }}"
- debug: msg="concated = {{ str_list | join(' ') }}"
- set_fact: concated_str="{{ str_list | join(' ') }}"
- debug: msg="assigned to a variable concated_str = {{ concated_str }}"
Try like this.
vars:
- img_path: "/var/lib/libvirt/images/{{ instance_name }}-disk2.img"
Where instance name is a another variable
This will do
- name: Generate JSON output based on template
template: src=colors.json.j2 dest=/tmp/colors.json
with_items: colors
It will generate a file like
{'colors':
{
'blue',
'red',
'green',
}
}
The to_json filter is what you want.
Ansible doesn't store variables as JSON strings, it stores them as Python objects. When you're debugging e.g. with -vvv, Ansible displays these Python objects as JSON strings, but that's for your convenience.
To produce the desired command:
- name: Execute php script with json parameter
vars:
# define my desired object
my_object:
colors: "{{ output.stdout_lines }}"
# my_object contains a dict with a single key
# "colors" who's value is the list contained
# in output.stdoutlines
command: "php -f /path/to/php/test.php {{ my_object | to_json | quote }}"
register: php_output
to_json takes the object stored in my_object and outputs a JSON string.
quote automatically handles any shell quoting that might be required for the JSON string. e.g. If my JSON string contains double-quotes, quote will wrap the whole JSON string in single-quotes.

Resources