I am trying to play a yml on other host than configured, using -l option.. but skipping: no hosts matched .
The scenario is where a host associated playbook is needed to be exceptionally used for some other host. (and for safety reasons, the playbook cannot have hosts:all and be left to the admin to limit the target(s))
What is the correct way to do this (if there is any)?
L.E. So, in the end, the answer of #mdaniel gave me the idea o a bash wrapper that creates a temp yml where the host: field is replaced with the argument.. it's not pretty but it works. (same works for a dynamical generation of a playbook from a series of tasks)
and the proper ansible way to do it i just found it here:
https://stackoverflow.com/a/18195217/624734
What is the correct way to do this?
Use all as the target of the playbook, and then constrain the hosts it applies to via the inventory, or the --limit that you mentioned
- hosts: all
# and now the rest of your playbook
You can try the below approach if you want to restrict hosts:all in your ansible script.
- hosts: "{{ host_group }}"
# Rest of your playbook
And you can have a specific group in your hosts file which you can use for testing.
For example,
[test]
192.168.1.1 # Test host
# Rest of your inventory file
And trigger the ansible playbook in the following order,
ansible-playbook -i hosts main.yml -e host_group="test"
I'm running an ansible playbook on a list of hosts with a host file:
[consul]
${HOST1} ansible_ssh_host=${HOST1} ansible_ssh_user=devops ansible_ssh_pass=blabla
${HOST2} ansible_ssh_host=${HOST2} ansible_ssh_user=devops ansible_ssh_pass=blabla
.......so on...
The thing is that I need to pass a different variable for each host.
I know of the flag -e that allows me to send a variable with the ansible-playbook command but it's not for each of the hosts.
I'm running the playbook with this:
ansible-playbook -vvvv site.yml
How can I pass a different var for each host?
Thanks!
Note: I'm using ansible 1.7.1
Two ways you should be able to do this:
1) Include the variable in your host file:
[consul]
${HOST1} ansible_ssh_host=${HOST1} .... myvar=x
${HOST2} ansible_ssh_host=${HOST2} .... myvar=y
2) Or use the include_vars task to load a file based on the host name
include_vars: "{{ ansible_ssh_host }}.yml"
The second method is good if you have a lot of variables to load for a host.
For more complex cases the lookups module might help:
http://docs.ansible.com/ansible/playbooks_lookups.html
I'm writing a one-off play to update some grok rule on my elasticsearch boxes (which I could be doing manually, but I anticipate doing a lot of these sorts of tasks so I want to get this working).
I simply want to run a lineinfile on 8 hosts:
#!/usr/bin/ansible-playbook -i /home/myself/workingdir/hosts
- name: update those lines
user: myself
hosts: elasticsearch_hosts
sudo: true
tasks:
- lineinfile:
dest: /opt/logstash/patterns/stuff
regexp: STUFF_AND_THINGS
line: STUFF_AND_THINGS [%{GREEDYDATA:big_h0nking_regex}\] ?.%{37_RANDOM_CHARACTERS:bologna} %{URIPATHPARAM:} %{*#&%$%#&#^$#:expletive}/%{NUMBER:some_more_crap} %{NUMBER:minutes_wasted} %{[idk,lol]1776}
- service: name=elasticsearch state=restarted
and here's my hosts file
192.168.1.98
192.168.2.76
192.168.3.54
192.168.4.32
192.168.5.10
192.168.6.123
[elasticsearch-hosts]
es-1 ansible_host=192.168.1.98
es-2 ansible_host=192.168.2.76
es-3 ansible_host=192.168.3.54
es-4 ansible_host=192.168.4.32
es-5 ansible_host=192.168.5.10
es-6 ansible_host=192.168.6.123
When this is run, I get:
PLAY [update them lines] ***************************************************
skipping: no hosts matched
I know I'm not doing something correctly in the inventory file, but I'm not sure what since I've only ever used, for example, the rackspace rax module to build an inventory list when the play is run.
It may also be useful to note that es-1,,6 are short names I have defined on dns, so I can do, for instance ssh es-1 and that works. It didn't seem to like those names on previous iterations of the file.
The issue is in how I'm calling ansible-playbook.
#!/usr/bin/ansible-playbook -i /home/myself/workingdir/hosts
This is not a proper way to pass an argument via shebang apparently, and as such ansible-playbook was defaulting to /etc/ansible/hosts, which is empty.
Consider if I want to check something quickly. Something that doesn't really need connecting to a host (to check how ansible itself works, like, including of handlers or something). Or localhost will do. I'd probably give up on this, but man page says:
-i PATH, --inventory=PATH
The PATH to the inventory, which defaults to /etc/ansible/hosts. Alternatively, you can use a comma-separated
list of hosts or a single host with a trailing comma host,.
And when I run ansible-playbook without inventory, it says:
[WARNING]: provided hosts list is empty, only localhost is available
Is there an easy way to run playbook against no host, or probably localhost?
Prerequisites. You need to have ssh server running on the host (ssh localhost should let you in).
Then if you want to use password authentication (do note the trailing comma):
$ ansible-playbook playbook.yml -i localhost, -k
In this case you also need sshpass.
In case of public key authentication:
$ ansible-playbook playbook.yml -i localhost,
And the test playbook, to get you started:
- hosts: all
tasks:
- debug: msg=test
You need to have a comma in the localhost, option argument, because otherwise it would be treated as a path to an inventory. The inventory plugin responsible for parsing the value can be found here.
You can define a default inventory with only localhost
See it is explained here:
https://docs.ansible.com/ansible/latest/reference_appendices/config.html#the-configuration-file
And in your playbook add use this
- hosts: all
connection: local
tasks:
- debug: msg=test
It will use local connection so no SSH is required (thus it doesn't expose your machine). It might be quicker unless you need to troubleshoot a ssh issue.
Also for quicker feedback loop you can use: gather_facts: no you already know your target.
I'm using Ansible for some simple user management tasks with a small group of computers. Currently, I have my playbooks set to hosts: all and my hosts file is just a single group with all machines listed:
# file: hosts
[office]
imac-1.local
imac-2.local
imac-3.local
I've found myself frequently having to target a single machine. The ansible-playbook command can limit plays like this:
ansible-playbook --limit imac-2.local user.yml
But that seems kind of fragile, especially for a potentially destructive playbook. Leaving out the limit flag means the playbook would be run everywhere. Since these tools only get used occasionally, it seems worth taking steps to foolproof playback so we don't accidentally nuke something months from now.
Is there a best practice for limiting playbook runs to a single machine? Ideally the playbooks should be harmless if some important detail was left out.
Turns out it is possible to enter a host name directly into the playbook, so running the playbook with hosts: imac-2.local will work fine. But it's kind of clunky.
A better solution might be defining the playbook's hosts using a variable, then passing in a specific host address via --extra-vars:
# file: user.yml (playbook)
---
- hosts: '{{ target }}'
user: ...
Running the playbook:
ansible-playbook user.yml --extra-vars "target=imac-2.local"
If {{ target }} isn't defined, the playbook does nothing. A group from the hosts file can also be passed through if need be. Overall, this seems like a much safer way to construct a potentially destructive playbook.
Playbook targeting a single host:
$ ansible-playbook user.yml --extra-vars "target=imac-2.local" --list-hosts
playbook: user.yml
play #1 (imac-2.local): host count=1
imac-2.local
Playbook with a group of hosts:
$ ansible-playbook user.yml --extra-vars "target=office" --list-hosts
playbook: user.yml
play #1 (office): host count=3
imac-1.local
imac-2.local
imac-3.local
Forgetting to define hosts is safe!
$ ansible-playbook user.yml --list-hosts
playbook: user.yml
play #1 ({{target}}): host count=0
There's also a cute little trick that lets you specify a single host on the command line (or multiple hosts, I guess), without an intermediary inventory:
ansible-playbook -i "imac1-local," user.yml
Note the comma (,) at the end; this signals that it's a list, not a file.
Now, this won't protect you if you accidentally pass a real inventory file in, so it may not be a good solution to this specific problem. But it's a handy trick to know!
This approach will exit if more than a single host is provided by checking the play_hosts variable. The fail module is used to exit if the single host condition is not met. The examples below use a hosts file with two hosts alice and bob.
user.yml (playbook)
---
- hosts: all
tasks:
- name: Check for single host
fail: msg="Single host check failed."
when: "{{ play_hosts|length }} != 1"
- debug: msg='I got executed!'
Run playbook with no host filters
$ ansible-playbook user.yml
PLAY [all] ****************************************************************
TASK: [Check for single host] *********************************************
failed: [alice] => {"failed": true}
msg: Single host check failed.
failed: [bob] => {"failed": true}
msg: Single host check failed.
FATAL: all hosts have already failed -- aborting
Run playbook on single host
$ ansible-playbook user.yml --limit=alice
PLAY [all] ****************************************************************
TASK: [Check for single host] *********************************************
skipping: [alice]
TASK: [debug msg='I got executed!'] ***************************************
ok: [alice] => {
"msg": "I got executed!"
}
There's IMHO a more convenient way.
You can indeed interactively prompt the user for the machine(s) he wants to apply by using vars_prompt:
---
- hosts: "{{ setupHosts }}"
vars_prompt:
- name: "setupHosts"
prompt: "Which hosts would you like to setup?"
private: false
tasks:
- shell: echo
A slightly different 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) }}"
No need to define an extra variable here, just run the playbook with the --limit flag.
ansible-playbook --limit imac-2.local user.yml
To expand on joemailer's answer, if you want to have the pattern-matching ability to match any subset of remote machines (just as the ansible command does), but still want to make it very difficult to accidentally run the playbook on all machines, this is what I've come up with:
Same playbook as the in other answer:
# file: user.yml (playbook)
---
- hosts: '{{ target }}'
user: ...
Let's have the following hosts:
imac-10.local
imac-11.local
imac-22.local
Now, to run the command on all devices, you have to explicty set the target variable to "all"
ansible-playbook user.yml --extra-vars "target=all"
And to limit it down to a specific pattern, you can set target=pattern_here
or, alternatively, you can leave target=all and append the --limit argument, eg:
--limit imac-1*
ie.
ansible-playbook user.yml --extra-vars "target=all" --limit imac-1* --list-hosts
which results in:
playbook: user.yml
play #1 (office): host count=2
imac-10.local
imac-11.local
I really don't understand how all the answers are so complicated, the way to do it is simply:
ansible-playbook user.yml -i hosts/hosts --limit imac-2.local --check
The check mode allows you to run in dry-run mode, without making any change.
AWS users using the EC2 External Inventory Script can simply filter by instance id:
ansible-playbook sample-playbook.yml --limit i-c98d5a71 --list-hosts
This works because the inventory script creates default groups.
Since version 1.7 ansible has the run_once option. Section also contains some discussion of various other techniques.
We have some generic playbooks that are usable by a large number of teams. We also have environment specific inventory files, that contain multiple group declarations.
To force someone calling a playbook to specify a group to run against, we seed a dummy entry at the top of the playbook:
[ansible-dummy-group]
dummy-server
We then include the following check as a first step in the shared playbook:
- hosts: all
gather_facts: False
run_once: true
tasks:
- fail:
msg: "Please specify a group to run this playbook against"
when: '"dummy-server" in ansible_play_batch'
If the dummy-server shows up in the list of hosts this playbook is scheduled to run against (ansible_play_batch), then the caller didn't specify a group and the playbook execution will fail.
This shows how to run the playbooks on the target server itself.
This is a bit trickier if you want to use a local connection. But this should be OK if you use a variable for the hosts setting and in the hosts file create a special entry for localhost.
In (all) playbooks have the hosts: line set to:
- hosts: "{{ target | default('no_hosts')}}"
In the inventory hosts file add an entry for the localhost which sets the connection to be local:
[localhost]
127.0.0.1 ansible_connection=local
Then on the command line run commands explicitly setting the target - for example:
$ ansible-playbook --extra-vars "target=localhost" test.yml
This will also work when using ansible-pull:
$ ansible-pull -U <git-repo-here> -d ~/ansible --extra-vars "target=localhost" test.yml
If you forget to set the variable on the command line the command will error safely (as long as you've not created a hosts group called 'no_hosts'!) with a warning of:
skipping: no hosts matched
And as mentioned above you can target a single machine (as long as it is in your hosts file) with:
$ ansible-playbook --extra-vars "target=server.domain" test.yml
or a group with something like:
$ ansible-playbook --extra-vars "target=web-servers" test.yml
I have a wrapper script called provision forces you to choose the target, so I don't have to handle it elsewhere.
For those that are curious, I use ENV vars for options that my vagrantfile uses (adding the corresponding ansible arg for cloud systems) and let the rest of the ansible args pass through. Where I am creating and provisioning more than 10 servers at a time I include an auto retry on failed servers (as long as progress is being made - I found when creating 100 or so servers at a time often a few would fail the first time around).
echo 'Usage: [VAR=value] bin/provision [options] dev|all|TARGET|vagrant'
echo ' bootstrap - Bootstrap servers ssh port and initial security provisioning'
echo ' dev - Provision localhost for development and control'
echo ' TARGET - specify specific host or group of hosts'
echo ' all - provision all servers'
echo ' vagrant - Provision local vagrant machine (environment vars only)'
echo
echo 'Environment VARS'
echo ' BOOTSTRAP - use cloud providers default user settings if set'
echo ' TAGS - if TAGS env variable is set, then only tasks with these tags are run'
echo ' SKIP_TAGS - only run plays and tasks whose tags do not match these values'
echo ' START_AT_TASK - start the playbook at the task matching this name'
echo
ansible-playbook --help | sed -e '1d
s#=/etc/ansible/hosts# set by bin/provision argument#
/-k/s/$/ (use for fresh systems)/
/--tags/s/$/ (use TAGS var instead)/
/--skip-tags/s/$/ (use SKIP_TAGS var instead)/
/--start-at-task/s/$/ (use START_AT_TASK var instead)/
'
I would suggest using --limit <hostname or ip>