How to dynamically set the hosts field in Ansible playbooks with a variable generated during execution? - variables

I am trying to test something at home with the variables mechanism Ansible offers, which I am about to implement in one of my projects at work. So, been searching for a while now, but seems I can't get it working that easily, even with others` solutions here and there.
I will represent my project logic at work now, by demonstrating with my test directory & files structure at home. Here's the case, I have the following playbooks:
main.yaml
pl1.yaml
pl2.yaml
Contents of ./main.yaml:
- import_playbook: /home/martin/ansible/pl1.yaml
- import_playbook: /home/martin/ansible/pl2.yaml
Contents of ./pl1.yaml:
- name: Test playbook 1
hosts: localhost
tasks:
- name: Discovering the secret host
shell: cat /home/martin/secret
register: whichHostAd
- debug:
msg: "{{ whichHostAd.stdout }}"
- name: Discovering my hostname
shell: hostname
register: myHostnameAd
- set_fact:
whichHost: "{{ whichHostAd.stdout }}"
myHostname: "{{ myHostnameAd.stdout }}"
cacheable: yes
- name: Test playbook 1 part 2
hosts: "{{ hostvars['localhost']['ansible_facts']['whichHost'] }}"
tasks:
- name: Structuring info
shell: hostname
register: secretHostname
- name: Showing the secret hostname
debug:
msg: "{{ secretHostname.stdout }}"
Contents of ./pl2.yaml:
- name: Test Playbook 2
hosts: "{{ whichHost }}"
tasks:
- name: Finishing up
shell: echo "And here am i again.." && hostname
- name: Showing var myHostname
debug:
msg: "{{ myHostname.stdout }}"
The whole idea is to have a working variable on the go at the hosts field between the plays. How do we do that?
The playbook does not run at all if I won't define the whichHost variable as an extra arg, and that's ok, I can do it each time, but during the execution I would like to have that variable manageable and changeable. In the test case above, I want whichHost to be used everywhere across the plays/playbooks included in main.yaml, specifically to reflect the output of the first task in pl1.yaml (or the output of the whichHostAd.stdout variable), so I can determine the host I am about to target in pl2.yaml.
According to docs, I should be able to at least access it with hostvars (as in my playbook), but this is the output I get when I try the above example:
ERROR! The field 'hosts' has an invalid value, which includes an undefined variable. The error was: 'dict object' has no attribute 'whichHost'
The error appears to have been in '/home/martin/ansible/pl1.yaml': line 22, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Test playbook 1 part 2
^ here
set_fact also does not seem to be very helpful. Any help will be appreciated!

Ok, I've actually figured it out pretty fast.
So, we definitely need to have a fact task, holding the actual data/output:
- hosts: localhost
tasks:
- name: Saving variable
set_fact:
whichHost: "{{ whichHostAd.stdout }}"
After that, when you want to invoke the var in other hosts and plays, we have to provide the host and the fact:
"{{ hostvars['localhost']['whichHost'] }}"
Like in my test above, but without ['ansible_facts']

Related

Ansible variables and tags

I have a playbook that calls 2 roles with shared variables. I'm using the roles to create some level of abstraction layer.
The problem happens when I try to call the role with the tags and variables which belong to another role I get an error. Also, I tried to use dependencies didn't work
Let me paste the code here to explain.
I have a role --> KEYS. Where I save my API calls to my 2 different platforms. As listed I'm registering the result to the user_result1 and user_result2
first role my_key.yml
# tasks file for list_users
- name: List Users platform 1
uri:
url: 'http://myhttpage.example.platform1'
method: GET
headers:
API-KEY: 'SOME_API_KEY'
register: user_result1
- name: List Users platform 2
uri:
url: 'http://myhttpage.example.platform2'
method: GET
headers:
API-KEY: 'SOME_API_KEY'
register: user_result2
Second role: list_users
- name: List users platform1
set_fact:
user: '{{ user | default([]) + [ item.email ] }}'
loop: "{{ user_result1.json }}"
- debug:
msg: "{{ user }}"
tags:
- user_1
- name: List users Cloudflare
set_fact:
name: "{{ name | default([]) + [item.user.email] }}"
loop: "{{ user_result2.result }}"
- debug:
msg: "{{ name }}"
tags:
- user_2
Playbook.yml
---
- name: Users
gather_facts: no
hosts: localhost
roles:
- my_key
- list_users
When I do the call without the --tags user_1 or user_2, it works fine.
However, when I do the call using the tags I got an error showing that variable user_result1 or user_result2 doesn't exist.
Any idea, please?
Thanks, Joe.
(#U880D basically answered the question but the OCD me wants to mark this as fixed so I'm typing this)
This is working as expected - --tags basically let you skip every task except those with the tag specified. See the official doc for more info on tags:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html
Echoing what #zeitounator said - if you want something to run unconditionally when --tag is used tag them with always.

Include variables generated in first play of playbook

I have a playbook which consists of two plays:
1: Create inventory file and variables file on localhost
2: Use the variables in commands on generated inventory
Example playbook:
---
- name: Generating inventory and variables
hosts: localhost
vars_files:
- variables.yml #variables file used for automating
tasks:
- name: Creating inventory template
template:
src: hosts.j2
dest: "./inventories/{{location}}/hosts"
mode: 0777
force: yes
ignore_errors: yes
run_once: true
- meta: refresh_inventory
- name: Creating predefined variables from a template
template:
src: predefined-vars.yml.j2
dest: "./variables/predefined-vars.yml"
- name: Setting vlan to network devices
remote_user: Ansible
hosts: all
vars_files:
- variables.yml #variables file used for automating.
- variables/predefined-vars.yml
tasks:
- name: configure Junos ROUTER for vlan
include_tasks: ./roles/juniper/tasks/add_vlan_rt.yml
when:
- inventory_hostname in groups['junos_routers']
- groups['junos_routers'] | length == 1
- location == inventory_name
This gives undefined variable error (for a variable created in the first play).
Is there a way to do this? I use this for generating variables like router_port_name and so on - the variables depend on location and dedicated server, which are defined in variables.yml
Any help is really appreciated.
Thanks
EDIT: However, I have noticed that this playbook:
---
- hosts: localhost
gather_facts: false
name: 1
vars_files:
- variables.yml
tasks:
- name: Creating predefined variables from a template
template:
src: predefined-vars.yml.j2
dest: "./variables/predefined-vars.yml"
- name: Generate hosts file
hosts: all
vars_files:
- variables.yml
- ./variables/predefined-vars.yml
tasks:
- name: test
debug: msg="{{ router_interface_name }}"
show the variables created in the first play.
The difference I see is that the first playbook reads all variable files (even predefined-vars.yml <- created at first play, used at the other) used in the playbook at the start of the first play (generating inventory and creating variable file) while the second playbook reads variables.yml in first play and only at the start of the second play reads the predefined-vars.yml .
Any Ideas how to make the first playbook behave the same way?
So I have found the solution to the problem, based on the documentation and suggestions from other people.
What I understood about the problem:
A playbook will read all the variables (of all plays) provided into the cache for later use, so if I include my predefined-vars.yml into vars_files, then after changing it in first play, the changes will not be used by later plays because they will use cache for that.
Thus I had to create another task in second play, which would read (load into cache) my newly generated file (for that play):
- name: Include predefined vars
include_vars: ./variables/predefined-vars.yml
run_once: true
Hope this helps you!
Still have no idea why second play shows the variables...

Using variable from set_fact in roles within same playbook

I'm stuck with using variable from tasks within roles in Ansible playbook. My playbook is following:
- hosts: server.com
gather_facts: yes
tasks:
- set_fact:
private_ip: "{{ item }}"
with_items: "{{ ansible_all_ipv4_addresses }}"
when: "item.startswith('10.')"
- debug: var=private_ip
roles:
- role: check-server
server_ip: 10.10.0.1
client_ip: "{{ private_ip }}
When pleybook is ran -debug shows correct IP inside the variable private_ip, but I can't make client_ip (from roles block) to get private_ip content. client_ip remains always undefined.
What sorcery can I apply here to have client_ip=$private_ip?
tasks are executed after roles are applied.
Change tasks to pre_tasks.
Besides, using set_fact in a loop is not the best practice. If you get the value you want, that's ok, I believe you verified it. But you should rather use (ansible_all_ipv4_addresses | select("match", "10\..*") | list)[0].

error while setting a fact or debug a webpage after registering this webpage in the get_uri module in Ansible

I have a problem every time while setting a fact for a registered webpage the error is :
u'redirected': False}]}: template error while templating string: unexpected char u'&' at 1238
The Playbook like:
- name: check webpage
uri:
url: http://{{ item.host }}.x.x.x
validate_certs: False
return_content: yes
status_code: 200
register: webpage3
with_items: "{{ servers }}"
when:
- lb is defined
- lb == "true"
- name: debug webpage
set_fact:
fact: "{{ webpage3 }}"
when:
- lb is defined
ignore_errors: yes
and my ansible version is ansible 2.2.1.0
So, do I have a problem with my webpage itself and is there's a solution to skip this error?
and after troubleshooting, I figured out that it fails because of a line starts with
<!-->
so how to skip this line with this character?
Thanks all, I have solved it by providing the dest in the uri module to a file then using sed to remove the unwanted characters and then registering the output to a new value to set_fact from and finally greping the needed value using a grep command. as it was hard to skip those chars in output

Ansible gets variable only from second execution

I've got a strange behavior of Ansible "copy" module when it is working with variables.
So, I have:
1. Config.yml:
- hosts: temp
vars_prompt:
- name: server_name
prompt: "Enter server number: 1, 2, 3..."
private: no
default: 5
- name: server_role
prompt: "Enter server role: app, admin"
private: no
default: admin
- name: server_type
prompt: "Enter server type: stage, prod"
private: no
default: stage
pre_tasks:
- name: Types and roles
set_fact:
servername: "{{ server_name }}"
serverrole: "{{ server_role }}"
servertype: "{{ server_type }}"
vars_files:
- "vars/variables"
roles:
- configs
"Configs" role with main.yml:
---
- set_fact: folder=server
when: serverrole == "app"
- set_fact: folder=admin-server
when: serverrole == "admin"
- set_fact: stageorprod=stage01
when: servertype == "stage"
- set_fact: stageorprod=prod
when: servertype == "prod"
- set_fact: fast={{ stageorprod }}/{{ folder }}/{{ servername }}
- name: Base copying admin-server
copy: src=admin-server/config dest=/home/tomcat/config/{{ fast }}/
when: serverrole == "admin"
Config files in ansible/roles/configs/files/admin-server/config.
When I run playbook with default values of variables (5, admin, stage), I've got:
TASK: [configs | set_fact fast={{stageorprod}}/{{folder}}/{{servername}}] *****
ok: [testcen04] => {"ansible_facts": {"fast": "stage01/admin-server/5"}, "item": ""}
TASK: [configs | Base copying admin-server] ***********************************
failed: [testcen04] => {"failed": true, "item": "", "md5sum": "cb2547d6235c078cfda365a5fb3c27c3",
"path": "/home/tomcat/config/stage01/admin-server/config", "state": "absent"}
msg: path /home/tomcat/config/stage01/admin-server/config does not exist
When I run this task one more time with same values, everything goes ok. But if I change some variable, it appears again.
I have noticed, that other modules, like "Template", works fine in same playbook with this variables. Maybe something wrong with "copy"?
As you can see, variable "fast" gets right values, but somehow, value of "servername" disappeared.
Your question is quite ambiguous to how you are executing your playbook and what you are trying to accomplish with the prompted variables. If you are trying to spin up servers, it's likely better to declare them in the inventory without prompting. If you are trying to access specific ones, you should likely be using groups to limit their range.
If you are using ansible to generate a set of hosts for you. You will likely want to store this information somewhere consistently, probably in a instance tags, a key-value store such as redis, database, or in files, before you spin up the hosts and bootstrap them. Then run a second playbook to include the role.
If you are not in a public cloud, and for some reason cannot tag instances or group them inventory, you can also try using facts.d to set the facts on the server and have them persist across runs, not just plays. Note that once you write to facts.d files, you should re-run setup module to gather facts again. Even though i use public cloud, I often make use of facts.d still.