How to avoid a playbook to run when no host matches? - ansible

Use case: users can provide a host name and will trigger a playbook run. In case the hostname has a typo I want to fail complete playbook run when "no hosts matched". I want to fail it since I like to detect a failure im Bamboo (which I use for CD/CI) to run the playbook.
I have done quite extensive research. It seems that it is a wanted behavior that the playbook exists with an exit code = 0 when no host matches. Here is one indication I found. I agree that the general behavior should be like this.
So I need for my use case an extra check. I tried the following:
- name: Deploy product
hosts: "{{ target_hosts }}"
gather_facts: no
any_errors_fatal: true
pre_tasks:
- name: Check for a valid target host
fail:
msg: "The provided host is not knwon"
when: target_hosts not in groups.tomcat_servers
But since there is no host match the playbook will not run, that is ok but it also ends with exit code 0. That way I can not fail the run in my automation system (Bamboo).
Due to this I am looking for a solution to throw an exit code != 0 when no host matches.

Add a play which would set a fact if a host matched, then check that fact in a second play:
- name: Check hosts
hosts: "{{ target_hosts }}"
gather_facts: no
tasks:
- set_fact:
hosts_confirmed: true
delegate_to: localhost
delegate_facts: true
- name: Verify hosts
hosts: localhost
gather_facts: no
tasks:
- assert:
that: hosts_confirmed | default(false)
- name: The real play
hosts: "{{ target_hosts }}"
# ...

Related

Run tasks on different hosts within an imported task

The calling playbook has:
- hosts: ssh_servers
tasks:
- import_tasks: create_files.yml
Then, in create_files.yml, I'd like to run some tasks on hosts other than ssh_servers, such as:
- Hosts: other_servers
tasks:
- file:
I get: ERROR! conflicting action statements: hosts, tasks .
Is this because I'm trying to run against hosts that were never included in the calling task ?
Is there a way to accomplish this other than in the calling playbook have:
- hosts:
- ssh_servers
- other_servers
tasks:
- import_tasks: create_files.yml
Thank you.
Is this because I'm trying to run against hosts that were never included in the calling task ? Is there a way to accomplish this other than in the calling playbook
I believe the answer is yes, although it'll be weird and could cause subsequent folks who interact with your playbook some confusion
given a hypothetical create_files.yml of:
- name: create /tmp/hello_world on hosts "not_known_at_launch_time"
file:
path: /tmp/hello_world
state: present
delegate_to: '{{ item }}'
with_items: '{{ groups["not_known_at_launch_time"] }}'
then the glue needed to bridge them together is the dynamic creation of a group and that delegate_to: keyword
- hosts: ssh_hosts
tasks:
- add_host:
groups: not_known_at_launch_time
name: secret-host-0
ansible_host: 192.168.1.1 # or whatever
# ... other hostvars here ...
- include_tasks: create_files.yml
it may be possible to combine those inside create_files.yml, via some shared vars: that say which host-and-ip should be added to the magic group name, which also has the benefit of keeping the magic group name localized to the file that consumes it.
BE AWARE, I did actually test this, but not extensively, so there may be some weird things such as the need to run_once: yes on them to keep the tasks from being run groups.ssh_hosts|length times or similar stuff
As Vladimir correctly pointed out, what you actually want to happen is to make that relationship formal:
- hosts: ssh_hosts
tasks:
... whatever tasks you had before
- add_host: ... as before ...
- hosts: anonymous_group_name_goes_here
tasks:
- include_tasks: create_files.yml
- hosts: ssh_hosts
tasks:
- debug:
msg: and now you are back to the ssh_hosts to pick up what they were supposed to be doing when you stopped to post on SO

Variable is defined but still getting undefined error

I am trying to write a playbook that completes some of its tasks on the machine that the playbook is running on. I know i can use local_action for this but I am just testing if the playbook works for now. Eventually I will need to use the delegate_to. I have defined the variable and I am using delegate_to: 'variable name' but I am getting this error. : " fatal [target node]: FAILED! => { msg": "'variablename' is undefined. Below is my playbook:
name: Create certs
gather_facts: true
vars:
master: "{{ nameofhost }}"
tasks:
- name: Run command
shell: Command to run
delegate_to: "{{ master }}"
You need to target your play to a target hosts of an inventory
name: Create certs
gather_facts: true
hosts: target_hosts
vars:
master: "{{ nameofhost }}"
tasks:
- name: Run command
shell: Command to run
delegate_to: "{{ master }}"
``` 
Your inventory files may look like that:
[target_hosts]
master ansible_host=your_master_dns_or_ip
And then ansible can target that inventory group and then reduce the task scope to master host. Or you can just use the localhost as target.

How to Execute Role Inside the Ansible Blocks

I have written a ansible playbook which do a deployment on the remote machines
- name: Deployment part of the script
vars:
hostName:
build_num:
hosts: "{{hostName}}"
become: true
serial: 1
tasks: this does deployment
after this I want to execute the util which is on localhost from where this playbook will be executed.
Now I have written a roles which does this for me if I execute them separately as a playbook
- name: Roles Demo
hosts: 127.0.0.1
connection: local
vars:
var1: "sometextvalue"
var2: "sometextvalue"
var3: "someurl"
roles:
- demorole #role which I created
Now I want to integrate the role in my main playbook mentioned at the top but I am getting
ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.
although its the same snippet which is working fine when run individually
Also I wanted to execute this using "Ansible blocks" like when a certain condition is matched execute a certain role but for that also I am getting the same above error just to summarize what I want to achieve using the blocks is as below
- name: Deployment part of the script
vars:
hostName:
build_num:
hosts: "{{hostName}}"
become: true
serial: 1
tasks: this does deployment complete
- name: Task for Doing some check
hosts: 127.0.0.1
connection: local
vars:
var1: "dakdkadadakdhkahdkahkdh
var2: "jdjaldjlaj"
var3: "djasjdlajdlajdljadljaldjlaj"
block:
- name: Doing Check for some1
roles:
- role1
when: x == "somevalue1"
- block:
- name: Doing check for some2
roles:
- role2
when: x == "somevalue2"
.
.
.
assuming the vars value are same
so I am not sure if this could be achieved
Using a block outside of the tasks section is not valid.
You can however execute roles from within the tasks section, which will allow you to use blocks and when conditionals however you choose.
Example:
- name: Task for Doing some check
hosts: 127.0.0.1
connection: local
vars:
var1: "dakdkadadakdhkahdkahkdh
var2: "jdjaldjlaj"
var3: "djasjdlajdlajdljadljaldjlaj"
tasks:
- name: Doing Check for some1
import_role:
name: role1
when: x == "somevalue1"
You will need to decide whether to use import_role or include_role. Take a look at https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse.html#dynamic-vs-static for an explanation of the differences.

Using conditionals in a Playbook to execute based on IP address

I have to change the windows computer name on 40 machines in AWS. I tried using the gather_facts to set a condition to only execute if the ip matches. But for some reason it doesn't pick it up. My solution so far to the problem(highly inefficient is to make individual host groups one per ip. I know there has to be a better way to go about this any input would be appreciated.
This is what I have that works
---
- hosts: windows_machine1
gather_facts: yes
tasks:
- name: Change the hostname to newname1
win_hostname:
name: newname1
register: res
- hosts: windows_machine2
tasks:
- name: Change the hostname to newname2
win_hostname:
name: newname2
register: res
- name: Reboot
win_reboot:
when: res.reboot_required
I tried two ways to make the conditional both resulted in a error when ran.
---
- hosts: windows_machine1
gather_facts: yes
tasks:
- name: Change the hostname to newname1
win_hostname:
name: newname1
register: res
when: ansible_facts['ansible_all_ipv4_addresses'] == '10.x.x.x
- name: Change the hostname to newname2
win_hostname:
name: newname2
register: res
when: ansible_facts['address'] == '10.x.x.x'
- name: Reboot
win_reboot:
when: res.reboot_required
It will fail saying the conditional check failed. Because my conditionals are bad. Does anyone know how to make a conditional based on ip?
Disclaimer: I only run Ansible on Linux hosts, so I guess there is a chance things are different on Windows hosts.
You don't specify ansible_facts, and instead just start at the specific root fact.
In the first case, the fact you are trying to get to is not going to help you, because it returns a list of all the IP's on the system. Even if there is only one, it still returns a list, which you can't then simply make a string comparison against.
This should do what you want in the first instance:
- name: Change the hostname to newname2
win_hostname:
name: newname2
register: res
when: "ansible_default_ipv4.address == '10.0.0.1'"
Are you intending to duplicate this block of code, one per host? If so consider setting a variable to lookup IP and new name:
- hosts: all
vars:
ip_newname:
10.0.0.1: newname1
10.0.0.2: newname2
10.0.0.3: newname3
tasks:
- name: Change the hostname
win_hostname:
name: "{{ ip_newname[ansible_default_ipv4.address] }}"
register: res
when: ansible_default_ipv4.address in ip_newname.keys()
- name: Reboot
win_reboot:
when: res is defined and res.reboot_required

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

Resources