Ansible: Limit a task in a playbook to localhost - ansible

I want to restrict an Ansible play to a specific host
Here's a cut down version of what I want:
- hosts some_host_group
tasks:
- name: Remove existing server files
hosts: 127.0.0.1
file:
dest: /tmp/test_file
state: present
- name: DO some other stuff
file:
...
I want to (as an early task), remove a local directory (I've created a file in the example as it's a more easily observed test). I was under the impression that I could limit a play to a set of hosts with the "hosts" parameter to the task -
but I get this error:
ERROR! 'hosts' is not a valid attribute for a Task
$ansible --version
ansible 2.3.1.0
Thanks.
PS I could wrap the ansible in a shell fragment, but that's ugly.

You should use delegate_to or local_action and tell Ansible to run the task only once (otherwise it will try to delete the directory as many times as target hosts in your play, although it won't be a problem).
You should also use absent not present if you want to remove directory, as you stated.
- name: Remove existing server files
delegate_to: 127.0.0.1
run_once: true
file:
dest: /tmp/test_file
state: absent

There are syntax errors in your playbook, have a look at Ansible Intro, Local Playbooks and Delegation.
- hosts: some_host_group
tasks:
- name: Remove existing server files
- hosts: localhost
tasks:
- file:
dest: /tmp/test_file
state: present
- name: DO some other stuff
file:

Related

Playbook to modify another playbook then run the modified playbook

I'm trying to create a playbook that will first modify another playbook, then run that modified version of the playbook. But import_playbook pre-processes, so the original playbook, not the modified, is run. Is there any way to tell Ansible to re-read this playbook? Note I am changing more that the hosts, but using this simple example.
- name: Change the playbook
hosts: localhost
connection: local
gather_facts: False
tasks:
- name: Change the playbook
lineinfile:
path: "my_playbook.yml"
regexp: "- hosts: some_group_name"
line: "- hosts: some_other_group_name"
- name: Include the playbook that was modified
import_playbook: my_playbook.yml
With proper organized inventory, group_vars, variables, templating and add_host or dynamic inventory, there is no need for self-modifying code (SMC).
Regarding
But import_playbook pre-processes, so the original playbook ...
you may have a look into What's the difference between include_tasks and import_tasks?.
Furthermore, there is no dynamic include_playbook.

Fail task/play when host not reachable Ansible

I have written a playbook which copies a file from source to destination on multiple hosts. The playbook works if all hosts are reachable but it doesn't fail if one of the host is unreachable.
ansible-playbook -i "10.11.12.13,10.11.12.14," -e "hostid=12345" test.yml
.e.g. if the host "10.11.12.13" is not reachable task execution skips the unreachable host and move onto the next host.
Sample playbook
- hosts: localhost
gather_facts: no
tasks:
- debug: msg="backup_restore.py file not found"
- name: Copy file
hosts: all
remote_user: test
gather_facts: no
vars:
srcFolder: "/home/test"
destFolder: "/opt/config"
tasks:
- block:
- name: Copy file to node
copy:
src: '{{srcFolder}}/self.config'
dest: '{{destFolder}}/self.config'
Is there a way to fail the task if the any of the hosts are not reachable. I am using ansible 2.6.1. Thank you in advance.
Brute force solution is any_errors_fatal
- name: Copy file
hosts: all
any_errors_fatal: true
Overview of other options is in Abort execution of remaining task if certain condition is failed.

Synchronize from localhost to another host which is not the playbook host

I'm running an Ansible playbook for host_a. Some tasks I delegate to host_b.
Now I would like to use the synchronize module to copy a directory from localhost to host_b. But delegate_to is the wrong option here, since this results in copying from host_b to host_a.
Is there a possibility to do that?
- hosts: host_a
tasks:
- name: rsync directory from localhost to host_b
synchronize:
# files on localhost
src: files/directory
dest: /directory/on/host_b
# delegate_to does not work here
# delegate_to: host_b
The only solution I can think of is deleting the target directory and then using a recursive copy with the copy module.
I couldn't find anything in the module documentation.
(Using ansible 2.4.2.0)
Doing this task in its own play for host_b is also not really an option because the variables I need for this task depend on host_a.
The easiest solution in this case is to use rsync command with local_action, i.e
- hosts: cache1
tasks:
- name: rsync directory from localhost to host_b
local_action: command rsync -az "{{ playbook_dir }}/files/directory" "{{ hostvars['host_b']['ansible_host'] }}:/directory/on/host_b"
{{ playbook_dir }} helps by not hardcoding paths on local system.

Ansible - use delegate_to and access environment variable from delegated host

I'm trying to look for a text pattern in a load balancer host from a worker host, using the following:
- name: A play
hosts: workers
tasks:
- name: Look for text pattern in delegated host
delegate_to: load-balancer-host
find:
paths: "$ENVIRONMENT_VARIABLE/subdir"
file_type: file
patterns: file.pattern
contains: 'text pattern'
register: aVariable
The problem is that I can't found any way to make $ENVIRONMENT_VARIABLE (this variable exists in the load-balancer-host) available for the play (it contains the directory, in load-balancer-host, from where I want to look for). ansible_env is only available for the workers but not for the load-balancer-host
I have tried...
- name: A play
hosts: workers
tasks:
- name: set fact
set_fact:
env_var: "{{ lookup('env', 'ENVIRONMENT_VARIABLE') }}"
delegate_to: load-balancer-host
- name: debug
debug:
msg: "{{ env_var }}"
... too, but it prints an empty string.
For users running Ansible 1.x, see kfreezy's answer.
For users running Ansible 2.x, I have found the following solution:
- hosts: workers
tasks:
- name: gather facts from lb
setup:
delegate_to: load-balancer-host
delegate_facts: false
This task will make $ENVIRONMENT_VARIABLE available in every worker ansible_env var. If you want to make $ENVIRONMENT_VARIABLE available in the load-balancer-host ansible_env, just set delegate_facts to True.
More info in ansible docs
Personally I would simplify your playbook by either adding the $ENVIRONMENT_VARIABLE as a variable in Ansible (probably in the host_vars for load-balancer-host) or running a play against load-balancer-host rather than use delegate_to. It might not make sense depending on what the other tasks are.
Here's a direct answer to your question though.
load-balancer-host's ansible_env will only be defined when the host is included in the playbook. You can add another play against the 'load-balancer-host' that will just gather facts. Then you can reference the facts from 'load-balancer-host' using hostvars in your subsequent plays against 'workers'. He's what it would look like.
- hosts: load-balancer-host
tasks:
- name: print debug message
debug:
msg: "this play is for gathering facts on the LB"
- name: A play
hosts: workers
tasks:
- name: Look for text pattern in delegated host
delegate_to: load-balancer-host
find:
paths: "{{ hostvars['load-balancer-host'].ansible_env.ENVIRONMENT_VARIABLE }}/subdir"
file_type: file
patterns: file.pattern
contains: 'text pattern'
register: aVariable

Override hosts variable of Ansible playbook from the command line

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 }}'

Resources