I am trying to have a block in Ansible which will invoke multiple playbooks using include if a specific flag is set. I am trying to achieve something similar to below:
- hosts: localhost
tasks:
- block:
- include: script1.yml
- include: script2.yml
- include: script3.yml
when: flag|bool
This snippet throws an error stating ERROR! unexpected parameter type in action: <type 'bool'>
Thanks in advance!
The plays can't be included in a block. Quoting from ansible.builtin.include Synopsis
Files with a list of plays can only be included at the top level.
As a result, only Play keywords can be applied. The condition when is not among them.
Related
I have a file containing some tasks. One of the tasks in this file uses include_role to execute a bunch of tasks from a role.
# tasks.yml
- name: task-1
include_role:
name: my-role
- name: task-2
...
I have my main playbook which calls the tasks in tasks.yaml from a with_items loop:
# main.yml
tasks:
- name: main-task
include_tasks: tasks.yaml
with_items: "{{ items.values()|list }}"
When this playbook is run I get the following errors:
ERROR! 'include_role' is not a valid attribute for a Play.
The error appears to have been in '.../tasks.yml': line 1, column 3, but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: task-1
^ here
Ansible appears to not like the way include_role is being used in the tasks file. I am not sure if the syntax is wrong somewhere or if doing it this way is not supported. Any ideas?
Test it with anither role first. I would remove the with_items for include as second option, may not work.
I would like to include multiple variables and tasks from my main task file, however I receive the following error when I attempt to do so:
ERROR! unexpected parameter type in action: <class 'ansible.parsing.yaml.objects.AnsibleSequence'>
...
The offending line appears to be:
- name: install and configure [application]
^ here
Here's my tasks/main.yml file in the role:
---
- name: install and configure [application]
include_vars:
- dependencies.yml
- directories.yml
- installations.yml
include_tasks:
- pre-setup.yml
- [application-23].yml
- database.yml
- [application-4].yml
- update.yml
- additional-components.yml
- ldap.yml
- test.yml
I suspect my formatting or syntax is invalid but I'm not precisely sure how to fix it.
I'd prefer to make my variables available globally at this time.
An ansible task can only do a single action, i.e. contain only one module call
The include_tasks module does not accept a list in the file/free-form parameter, only one single file name
The include_vars module can eventually read several files if you load a full directory with the dir option.
There are filenames that look a little weird in your included tasks and might cause errors. Do your file names really contain square brackets ([]) ? They are markers for lists and might be interpreted as such.
From the info I have so far, this is all I can propose to fix your current failing task:
- name: Include variables
include_vars: "{{ item }}"
loop:
- dependencies.yml
- directories.yml
- installations.yml
- name: Play needed tasks in order
include_tasks: "{{ item }}"
loop:
- pre-setup.yml
- application-23.yml
- database.yml
- application-4.yml
- update.yml
- additional-components.yml
- ldap.yml
- test.yml
Meanwhile, I suggest you take some time to read the doc a little more and maybe get familiar with the concept of roles as the above does not look like a good design at first glance.
I am including another playbook from my playbook. I need to pass it a variable, called required_node_version. I am using the same variable in my playbook, so was expecting that the included playbook will inherit it. But it doesn't causing 'undefined' error. So I tried to use this not very elegant solution:
- hosts: localhost
connection: local
vars:
required_node_version: v6.3.1
...
- include: ../../base/ci/build.yml
vars:
required_node_version: "{{required_node_version}}"
Which causes {"failed": true, "msg": "ERROR! recursive loop detected in template string: {{required_node_version}}"}.
Any elegant solution for this?
Ansible maintains the state of each play separately. Variable scope of one play is also separated from other plays. you may consider variable scope defined in a play as a directed acyclic graph where each node name is unique and values will be overwritten on second write.
When you include some other playbook in a playbook (including the playbook at top level), the included playbook will have its own variable scope because its a completely different play. So you won’t be able to access the variables defined in the main play . The actual error is not recursive loop detected error, the actual error is variable undefined error because you are trying to access a variable that is defined in some other play.
Try this:
main.yml
---
- hosts: all
vars:
name: shasha
- include: included_playbook.yml
vars:
name1: "{{name}}""
Now you will get the actual error which is : name is undefined, because you are trying to access a variable from different scope(different play) while assigning name to name1.
Now change your main.yml with this:
main.yml
---
- hosts: all
vars:
name: shasha
- include: included_playbook.yml
vars:
name: shashank
Though the name is defined twice, they are different than each other because the scope for both are different (scope of main play and the scope of included play). So this works fine without any issues.
Some alternate solutions:
1. Pass the variable while invoking the playbook:
Since the playbook you are including can also be run as a main playbook, in that case how would you use that variable ? Either you will define the variable inside that playbook or you will pass it from command line.
When you pass the variable while invoking playbook,it will automatically create that variable in scope of both playbooks (the main playbook as well as the included one):
ansible-playbook -i hosts main.yml -e "required_node_version=v6.3.1"
Or maybe you can create a JSON file which contains all the common variables which are common across all the playbooks you are including and then pass that JSON file while invoking the main ansible playbook.
2. Redesign you Ansible playbooks:
In case the ansible playbook that you are including, works as a helper and not being used in standalone manner, you should then include this playbook at task level. You will still maintain the re-usability and there will be only single scope so you don't need to worry about passing variable explicitly.
A little more about the reason behind recursive loop detected error:
Ansible actually does not use the concept of rvalue when you assign a variable to a variable, It performs the binding between the two instead. For example, What do you think would be the value of variable name1 in the following code:
main.yml
---
- hosts: all
vars:
- name: old_val
- name1: "{{name}}"
- name: new_val
tasks:
- debug: msg="{{name1}}"
If your answer is new_val, you understood the logic. Consider name1 as a reference, which is referring to name and name is now referring to new_val. So the only value present is new_value and name1 and name are just the references referring to new_value.
reference to old_value is lost.
Now consider the below script:
main_recursive.yml
---
- hosts: all
vars:
- name: shasha
- name1: "{{name}}"
- name: "{{name1}}"
tasks:
- debug: msg="{{name}}"
Now let's try resoving the value of name. name is pointing to name1 and name1 is pointing to name. That's ok but how are we going to reach the actual value ??? No way, its a recursive loop !!!
A bit of documentation:
Ansible has 3 main scopes:
Global: this is set by config, environment variables and the command line
Play: each play and contained structures, vars entries, include_vars, role defaults and vars.
Host: variables directly associated to a host, like inventory, facts or registered task outputs
So variables you define for each play don't know about each other.
You have two options:
set required_node_version globally (via -e switch), so it will be defined everywhere
set required_node_version for each individual host (i.e. via ./group_vars/all.yml file), so it will be available in each task as host-bound fact.
# "Run application specific configuration scripts"
- include: "app_cfg/{{ app_name }}.yml"
when: "{{ app_conf[app_name].app_cfg }}"
ignore_errors: no
tags:
- conf
I thought that I will be able conditionally include application specific playbooks simply by setting one variable to the true/false value like so:
app_conf:
my_app_1:
app_cfg: no
my_app_2:
app_cfg: yes
Unfortunately Ansible is forcing me to create file beforehand:
ERROR: file could not read: <...>/tasks/app_cfg/app_config.yml
Is there a way I can avoid creating a bunch of empty files?
# ansible --version
ansible 1.9.2
include with when is not conditional in common sense.
It actually includes every task inside include-file and append given when statement to every task included task.
So it expects include-file to exist.
You can try to handle this using with_first_found and skip: true.
Something like this:
# warning, code not tested
- include: "app_cfg/{{ app_name }}.yml"
with_first_found:
- files:
- "{{ app_conf[app_name].app_cfg | ternary('app_cfg/'+app_name+'.yml', 'unexisting.file') }}"
skip: true
tags: conf
It supposed to supply valid config name if app_cfg is true and unexisting.file (which will be skipped) otherwise.
See this answer about skip option.
I would like to have a master playbook, which include's other playbooks. Is it possible to pass a variable to that included playbook?
The normal syntax which is used for passing variables to included tasks doesn't work (see below)
- include: someplaybook.yml variable=value
and
- include: someplaybook.yml
vars:
variable: value
I'm running v2.0.2.0.
The only thing i see missing is quotes.
- include: someplaybook.yml variable='value'
It works for me and should work for you too. If not share the error you face.
Make sure you have this variable "variable" defined in the task of the role as well and from here you are just passing the value to that variable.
Tested on ansible 2.4
- import_playbook: any_playbook.yml variable='value'
Also, I suggest you read this,
http://docs.ansible.com/ansible/latest/playbooks_reuse.html
and try using roles in this case, it'll help in a case like this, where you're trying to include/import multiple playbooks in a single main playbook.
And about passing a value to the include statement you can add it to the vars main.yml of the role.
Or, if the variable you want to pass is the result of a previous task in the single main playbook use 'register' and save the output in a varible.
- debug: msg="{{result.stdout_lines}}"
here, result is the registered variable.
Use the debug module to know exactly what you want to pass to the playbook.
Hope this helps.
In my opinion most consistent way to pass variable to included playbook and to another play in the current playbook as well is using:
set_fact:
global_var_name: "{{ your_var }}"
before
- import_playbook: your_playbook.yml
After you set the fact it's is accessible from any play in the playbook and imported playbooks, for instance inside "your_playbook" you can call it like so:
debug:
var: global_var_name
If you use:
- import_playbook: someplaybook.yml variable='value'
you can only pass fixed 'value'(in include as well) if you try to pass var value:
- import_playbook: someplaybook.yml internal_var="{{ your_var }}"
you will get NOT DEFINED when you call internal_var inside 'someplaybook.yml'
All of this is true for the beginning of 2021, ansible 2.9*, it's quite possible they will 'fix' import_playbook, I would like this, by the way.