I have re-written the question to be more specific instead of using a generic example of what I am trying to achieve, as per #Zeitounator's suggestion.
I use ansible to spin up VM's in VMware by adding a new entry in the hosts.ini file and running ansible-playbook -i inventory/dev/hosts.ini --limit SomeGroup playbooks/site.yml
The vmware role (calledvmware) will
* check to see if the VM already exists.
* If it does, then obviously it does not create the VM.
* If it does not exist, then it will create the VM from a template.
To destroy a VM, I run this: ansible-playbook -i inventory/dev/hosts.ini --limit SomeGroup playbooks/site.yml -e 'vmware_destroy=true'
That works as intended. Now for my issue.
When this variable is set (vmware_destroy=true), it will destroy the VM successfully, BUT ansible will attempt to carry on with the rest of the playbook on the host that has just been destroyed. Obviously it fails. The playbook does actually stop due to the failure. But not gracefully.
Here is an example of the flow of logic:
$ cat playbooks/site.yml
---
- hosts: all
gather_facts: no
roles:
- { role: vmware, tags: vmware }
- hosts: all
gather_facts: yes
roles:
- { role: bootstrap, tags: bootstrap }
- { role: common, tags: common }
- hosts: AppServers
gather_facts: no
roles:
- { role: application }
# and so on.
$ cat playbooks/roles/vmware/main.yml
---
# Checks to see if the VM exists already.
# A variable `found_vm` is registered in this task.
- import_tasks: find.yml
# Only import this task when all of the `when` conditions are met.
- import_tasks: destroy.yml
when:
- vmware_destroy is defined
- vmware_destroy # Meaning 'True'
- found_vm
# If the above is true, it will not import this task.
- import_tasks: create.yml
when:
- found_vm.failed
- vmware_destroy is not defined
So, the point is, when I specify -e 'vmware_destroy=true', ansible will attempt to run the rest of the playbook and fail.
I don't want ansible to fail. I want it to stop gracefully after completing the vmware role based on -e 'vmware_destroy=true flag provided on the command line.
I am aware I can use a different playbook for this, something like:
ansible-playbook -i inventory/dev/hosts.ini --limit SomeGroup playbooks/VMWARE_DESTROY.yml. But I would rather use a variable as opposed to a separate playbook. If there is a strong argument to split out the playbook in this way, please provide references.
Please let me know if more clarification is needed.
Thank you in advance.
A playbook is the top Ansible abstraction layer (playbook -> role -> task -> ...). There are 2 more layers in AWX (workflow -> job template -> playbook ...) to control the playbooks. To follow the architecture either AWX, or any other tool to interface Ansible (ansible-runner, or scripts for example) should be used to control the playbooks.
Controlling of the playbooks inside Ansible is rather awkward. Create 2 playbooks vmware-create.yml and vmware-destroy.yml
$ cat vmware-create.yml
- hosts: all
gather_facts: yes # include cached variables
tasks:
- block:
- debug:
msg: Don't create VM this time. End of play.
- meta: end_play
when: not hostvars['localhost'].vmware_create
- debug:
msg: Create VM.
$ cat vmware-destroy.yml
- hosts: all
gather_facts: yes # include cached variables
tasks:
- block:
- debug:
msg: Don't destroy VM this time. End of play.
- meta: end_play
when: not hostvars['localhost'].vmware_destroy
- debug:
msg: Destroy VM.
and import them into the playbook vmware_control.yml. See below
$ cat vmware-control.yml
- hosts: localhost
vars:
vmware_create: true
vmware_destroy: false
tasks:
- set_fact:
vmware_create: "{{ vmware_create }}" # cache variable
- set_fact:
vmware_destroy: "{{ vmware_destroy }}" # cache variable
- import_playbook: vmware-create.yml
- import_playbook: vmware-destroy.yml
Control the flow with the variables vmware_create and vmware_destroy. Run vmware_control.yml at localhost and declare hosts: all inside vmware-create.yml and vmware-destroy.yml.
I have a requirement to setup environment on multiple hosts. I need to read variables from a file and I want them to execute on a particular hosts or a set of hosts. I am unable to make out how can I use single task to achieve this?
Assuming I have a file which contains variables in a following format:
container_name: java1
container_hostname: java-mc1
script_location: /tmp
execute_on_hosts: host1,host2,host3
container_name: java2
container_hostname: java-mc2
script_location: /tmp
execute_on_hosts: host1
I want my task to read from this file and execute the task on specified hosts only by matching hostname in hosts section (provided in playbook).
You should read the Ansible Variables docs. Whilst you can probably get something to work, you are not really using Ansible the way it was intended.
Ansible fundamentally is built around the concept of 'plays'. Each play represents one or more tasks, that should be targetted at a host or group of hosts. Those plays are then packaged up into a 'playbook'. Ansible provides methods to organise variables in the 'Inventory'. A common approach would be to do something like:
/some/ansible/dir/hosts
[mc1_hosts]
host1
host2
host3
[mc2_hosts]
host1
/some/ansible/dir/group_vars/mc1_hosts
---
mc1_container_data:
container_name: java1
container_hostname: java-mc1
script_location: /tmp
/some/ansible/dir/group_vars/mc2_hosts
---
mc2_container_data:
container_name: java2
container_hostname: java-mc2
script_location: /tmp
/some/ansible/dir/playbook.yml
---
- hosts: mc1_hosts
tasks:
- name: Display variables
debug:
msg: "{{ mc1_container_data }}"
- name: Display container_name
msg: "{{ mc1_container_data.container_name }}"
- hosts: mc2_hosts
tasks:
- name: Display variables
debug:
msg: "{{ mc2_container_data }}"
- name: Display container_name
msg: "{{ mc2_container_data.container_name }}"
And finally ansible-playbook playbook.yml to run those tasks.
I have a task which has meaning only if it has been run with full list of hosts (creating a /etc/ssh/ssh_known_hosts file). I want to skip this task if '--limit' has been used.
Is any way in ansible to detect if --limit option was used?
After some struggles I found a way:
- hosts: all
gather_facts: no
tasks:
- set_fact:
full_run: '{{play_hosts == groups.all}}'
...
- hosts: other_group
tasks:
- template: ...
when: full_run
I'd like to pass a variable to an included Ansible playbook as follows:
---
- hosts: localhost
connection: local
vars:
my_group: foo
- include: site.yml hosts={{ my_group }}
Then, in site.yml...
---
- hosts: "{{ hosts }}"
...
Unfortunately, I get an error saying that my_group is undefined in site.yml. Ansible docs do say that:
Note that you cannot do variable substitution when including one playbook inside another.
Is this my case? Is there a way around it?
You can use this syntax, but my_group has to be defined at the global level. Now it's local to the first play - it's even clear from the indentation.
You can confirm this by running your playbook with --extra-vars my_group=foo.
But generally what you seem to want to achieve is done using in-memory inventory files and add_host module. Take this as an example:
- hosts: localhost
gather_facts: no
vars:
target_host: foo
some_other_variable: bar
tasks:
- add_host:
name: "{{ target_host }}"
groups: dynamically_created_hosts
some_other_variable: "{{ some_other_variable }}"
- include: site.yml
with site.yml:
---
- hosts: dynamically_created_hosts
tasks:
- debug:
var: some_other_variable
I added some_other_variable to answer the question from your comment "how do I make a variable globally available from inside a play". It's not global, but it's passed to another play as a "hostvar".
From what I see (and I can't explain why) in Ansible >=2.1.1.0, there must be an inventory file specified for the dynamic in-memory inventory to work. In older versions it worked with Ansible executed ad hoc, without an inventory file, but now you must run ansible-playbook with -i inventory_file or have an inventory file defined through ansible.cfg.
This is a fragment of a playbook that I'm using (server.yml):
- name: Determine Remote User
hosts: web
gather_facts: false
roles:
- { role: remote-user, tags: [remote-user, always] }
My hosts file has different groups of servers, e.g.
[web]
x.x.x.x
[droplets]
x.x.x.x
Now I want to execute ansible-playbook -i hosts/<env> server.yml and override hosts: web from server.yml to run this playbook for [droplets].
Can I just override as a one time off thing, without editing server.yml directly?
Thanks.
I don't think Ansible provides this feature, which it should. Here's something that you can do:
hosts: "{{ variable_host | default('web') }}"
and you can pass variable_host from either command-line or from a vars file, e.g.:
ansible-playbook server.yml --extra-vars "variable_host=newtarget(s)"
For anyone who might come looking for the solution.
Play Book
- hosts: '{{ host }}'
tasks:
- debug: msg="Host is {{ ansible_fqdn }}"
Inventory
[web]
x.x.x.x
[droplets]
x.x.x.x
Command: ansible-playbook deplyment.yml -i hosts --extra-vars "host=droplets"
So you can specify the group name in the extra-vars
We use a simple fail task to force the user to specify the Ansible limit option, so that we don't execute on all hosts by default/accident.
The easiest way I found is this:
---
- name: Force limit
# 'all' is okay here, because the fail task will force the user to specify a limit on the command line, using -l or --limit
hosts: 'all'
tasks:
- name: checking limit arg
fail:
msg: "you must use -l or --limit - when you really want to use all hosts, use -l 'all'"
when: ansible_limit is not defined
run_once: true
Now we must use the -l (= --limit option) when we run the playbook, e.g.
ansible-playbook playbook.yml -l www.example.com
Limit option docs:
Limit to one or more hosts This is required when one wants to run a
playbook against a host group, but only against one or more members of
that group.
Limit to one host
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit "host1"
Limit to multiple hosts
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit "host1,host2"
Negated limit.
NOTE: Single quotes MUST be used to prevent bash
interpolation.
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit 'all:!host1'
Limit to host group
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit 'group1'
This is a bit late, but I think you could use the --limit or -l command to limit the pattern to more specific hosts. (version 2.3.2.0)
You could have
- hosts: all (or group)
tasks:
- some_task
and then ansible-playbook playbook.yml -l some_more_strict_host_or_pattern
and use the --list-hosts flag to see on which hosts this configuration would be applied.
An other solution is to use the special variable ansible_limit which is the contents of the --limit CLI option for the current execution of Ansible.
- hosts: "{{ ansible_limit | default(omit) }}"
If the --limit option is omitted, then Ansible issues a warning, but does nothing since no host matched.
[WARNING]: Could not match supplied host pattern, ignoring: None
PLAY ****************************************************************
skipping: no hosts matched
I'm using another approach that doesn't need any inventory and works with this simple command:
ansible-playbook site.yml -e working_host=myhost
To perform that, you need a playbook with two plays:
first play runs on localhost and add a host (from given variable) in a known group in inmemory inventory
second play runs on this known group
A working example (copy it and runs it with previous command):
- hosts: localhost
connection: local
tasks:
- add_host:
name: "{{ working_host }}"
groups: working_group
changed_when: false
- hosts: working_group
gather_facts: false
tasks:
- debug:
msg: "I'm on {{ ansible_host }}"
I'm using ansible 2.4.3 and 2.3.3
I changed mine to default to no host and have a check to catch it. That way the user or cron is forced to provide a single host or group etc. I like the logic from the comment from #wallydrag. The empty_group contains no hosts in the inventory.
- hosts: "{{ variable_host | default('empty_group') }}"
Then add the check in tasks:
tasks:
- name: Fail script if required variable_host parameter is missing
fail:
msg: "You have to add the --extra-vars='variable_host='"
when: (variable_host is not defined) or (variable_host == "")
Just came across this googling for a solution. Actually, there is one in Ansible 2.5. You can specify your inventory file with --inventory, like this: ansible --inventory configs/hosts --list-hosts all
If you want to run a task that's associated with a host, but on different host, you should try delegate_to.
In your case, you should delegate to your localhost (ansible master) and calling ansible-playbook command
I am using ansible 2.5 (2.5.3 exactly), and it seems that the vars file is loaded before the hosts param is executed. So you can set the host in a vars.yml file and just write hosts: {{ host_var }} in your playbook
For example, in my playbook.yml:
---
- hosts: "{{ host_name }}"
become: yes
vars_files:
- vars/project.yml
tasks:
...
And inside vars/project.yml:
---
# general
host_name: your-fancy-host-name
Here's a cool solution I came up to safely specify hosts via the --limit option. In this example, the play will end if the playbook was executed without any hosts specified via the --limit option.
This was tested on Ansible version 2.7.10
---
- name: Playbook will fail if hosts not specified via --limit option.
# Hosts must be set via limit.
hosts: "{{ play_hosts }}"
connection: local
gather_facts: false
tasks:
- set_fact:
inventory_hosts: []
- set_fact:
inventory_hosts: "{{inventory_hosts + [item]}}"
with_items: "{{hostvars.keys()|list}}"
- meta: end_play
when: "(play_hosts|length) == (inventory_hosts|length)"
- debug:
msg: "About to execute tasks/roles for {{inventory_hostname}}"
This worked for me as I am using Azure devops to deploy an application using CICD pipelines. I had to make this hosts (in yml file) more dynamic so in release pipeline I can add it's value, for example:
--extra-vars "host=$(target_host)"
pipeline_variable
My ansible playbook looks like this
- name: Apply configuration to test nodes
hosts: '{{ host }}'