I have an inventory file
[hosttype1]
Server
Server2
[hosttype2]
server3
server4
I am trying to read this as a whole into ansible. The file is used in the normal way to define hosts and types for code deploy, but I need to also read the full content for adding into a config file.
the inventory file differs for different environments being deployed, so there is no fixed location for it.
Is it possible to read the file and create a variable that I can then loop over and use the hosttype and a comparitor?
The easiest way is to use it as an inventory. For example
shell> cat hosts
[hosttype1]
Server
Server2
[hosttype2]
server3
server
shell> cat pb.yml
- hosts: localhost
vars:
my_groups: "{{ groups|difference(['all', 'ungrouped']) }}"
my_hosts: "{{ my_groups|map('extract', groups)|list }}"
my_inventory: "{{ dict(my_groups|zip(my_hosts)) }}"
tasks:
- debug:
var: my_inventory
gives
shell> ansible-playbook -i hosts pb.yml
PLAY [localhost] ******************************************************
TASK [debug] **********************************************************
ok: [localhost] =>
my_inventory:
hosttype1:
- Server
- Server2
hosttype2:
- server3
- server
Related
I have to pass the host on which the Ansible command will be executed through extra vars.
I don't know in advance to which hosts the tasks will be applied to, and, therefore, my inventory file is currently missing the hosts: variable.
If I understood from the article "How to pass extra variables to an Ansible playbook" correctly, overwriting hosts is only possible by having already composed groups of hosts.
From the post Ansible issuing warning about localhost I gathered that referencing hosts to be managed in an Ansible inventory is a must, however, I still have doubts about it since the usage of extra vars was not mentioned in the given question.
So my question is: What can i do in order to make this playbook work?
- hosts: "{{ host }}"
tasks:
- name: KLIST COMMAND
command: klist
register: klist_result
- name: TEST COMMAND
ansible.builtin.shell: echo hi > /tmp/test_result.txt
... referencing hosts to be managed in an Ansible inventory is a must
Yes, that's the case. Regarding your question
What can I do in order to make this playbook work? (annot. without a "valid" inventory file)
you could try with the following workaround.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- add_host:
hostname: "{{ target_hosts }}"
group: dynamic
- hosts: dynamic
become: true
gather_facts: true
tasks:
- name: Show hostname
shell:
cmd: "hostname && who am i"
register: result
- name: Show result
debug:
var: result
A call with
ansible-playbook hosts.yml --extra-vars="target_hosts=test.example.com"
resulting into execution on
TASK [add_host] ***********
changed: [localhost]
PLAY [dynamic] ************
TASK [Show hostname] ******
changed: [test.example.com]
In any case it is recommended to check how to build your inventory.
Further Documentation
add_host module – Add a host (and alternatively a group) to the ansible-playbook in-memory inventory
Lets say I have got inventory file like this
inventory.txt
abc
cde
def
[check1:children]
abc
[check2:children]
cde
[check3: children]
def
Now I will take input from user eg: check1,check3 separated by comma in a variable and then I want to run my next play on those groups check1,check3.
How can I achieve this?
A comma separated list of groups or hosts is a perfectly valid pattern for the hosts of a playbook.
So you can just pass it directly in the hosts attribute of your playbook:
- hosts: "{{ user_provider_hosts }}"
gather_facts: no
tasks:
- debug:
Then, you just have to add this values in the --extra-vars (or short, -e) flag of the playbook command:
ansible-playbook play.yml --extra-vars "user_provider_hosts=check1,check3"
This would yield:
TASK [debug] ******************************************************************
ok: [abc] =>
msg: Hello world!
ok: [def] =>
msg: Hello world!
Another option is to target all hosts:
- hosts: all
gather_facts: no
tasks:
- debug:
And use the purposed --limit flag:
ansible-playbook play.yml --limit check1,check3
A third option would be to use a play targeting localhost to prompt the user for the groups to target, then use a fact set by localhost to target those groups in another play:
- hosts: localhost
gather_facts: no
vars_prompt:
- name: _hosts
prompt: On which hosts do you want to act?
private: no
tasks:
- set_fact:
user_provider_hosts: "{{ _hosts }}"
- hosts: "{{ hostvars.localhost.user_provider_hosts }}"
gather_facts: no
tasks:
- debug:
Would interactively ask for hosts and act on the user provider hosts:
On which hosts do you want to act?: check1,check3
PLAY [localhost] **************************************************************
TASK [set_fact] ***************************************************************
ok: [localhost]
PLAY [check1,check3] **********************************************************
TASK [debug] ******************************************************************
ok: [abc] =>
msg: Hello world!
ok: [def] =>
msg: Hello world!
ansible version: 2.9
Hi.
How can I specify the hosts when I import a playbook with import_playbook?
My code (/project/first_pb.yml)
- import_playbook: /test/pb0.yml
hosts: atlanta
Q: "A method to pass specific family hosts to the imported playbook?"
A: There is no difference between a playbook imported or not. For example,
shell> cat pb-A.yml
- hosts: "{{ my_hosts|default('localhost') }}"
tasks:
- debug:
var: inventory_hostname
shell> ansible-playbook pb-A.yml -e my_hosts=host1
...
inventory_hostname: host1
shell> cat pb-B.yml
- import_playbook: pb-A.yml
shell> ansible-playbook pb-B.yml -e my_hosts=host1
...
inventory_hostname: host1
There are many options on how to pass specific hosts and groups to a playbook. For example, see:
Patterns: targeting hosts and groups
add_host module – Add a host and group to the ansible-playbook in-memory inventory
Inventory plugins (e.g. constructed)
I can filter play plabooks with "when: " condition, for example:
- import_playbook: /test/pb0.yml
when: hostname != host1a*
- import_playbook: /test/pb0.yml
when: '"north" not in hostname'
- import_playbook: /test/pb0.yml
when: '"west" in hostname'
I'm looking for a way to specify multiple groups as hosts in an Ansible playbook when the groups are located in separate inventories (this is similar to this question, but different because that question assumes one single inventory).
Say I have a playbook called change_things.yml. Sometimes I want to change things in development, sometimes qa, sometimes production, etc.:
ansible-playbook -i development -i production change_things.yml.
Say there are separate inventories which look roughly like this:
# development inventory
[development]
10.0.0.1
10.0.0.2
The playbook above fails to run when hosts is not explicitly specified.
I have a few problems:
Using hosts: all seems harmful. If a user forgets to explicitly declare an inventory, I would imagine that Ansible inherits from whatever is in /etc/ansible/hosts.
Hard-coding host groups (hosts: development:production) is undesirable because I may want to run something like ansible-playbook -i development -i qa change_things.yml in the future.
I'm looking for a way to maintain separate inventories of hosts, but create playbooks in such a way that they can be executed against multiple combinations of host groups. I do not know how to tell Ansible "use these groups from these inventories".
Q: "Tell Ansible 'use these groups from these inventories'"
A: Dynamically create a new group of hosts in the first play and use it afterward. For example
shell> cat pb.yml
- hosts: localhost
tasks:
- add_host:
name: "{{ item }}"
groups: my_group
loop: "{{ my_groups|from_yaml|map('extract', groups)|flatten }}"
- hosts: my_group
tasks:
- debug:
var: inventory_hostname
Given the inventories
shell> cat hosts_prod
[prod]
prod1
prod2
prod3
shell> cat hosts_qa
[qaA]
qa1
qa2
[qaB]
qa3
shell> cat hosts_devel
[develA]
devel1
devel2
[develB]
devel3
The command
ansible-playbook pb.yml -i hosts_prod -i hosts_qa -i hosts_devel -e "my_groups=[develA,qaB]"
gives (abridged)
PLAY [my_group] **********************************************
TASK [debug] **************************************************
ok: [devel2] =>
inventory_hostname: devel2
ok: [devel1] =>
inventory_hostname: devel1
ok: [qa3] =>
inventory_hostname: qa3
Define any combination of inventories and my_groups on the command-line.
Well if you are indeed separating your different servers in different inventories, then you can name the groups with something common like servers
And then your inventory will follow
## this is development inventory
[servers]
node1.dev.example.org
node2.dev.example.org
## this is qa inventory
[servers]
node1.qa.example.org
node2.qa.example.org
And so your playbook will begin with
- hosts: servers
And you can run this with
ansible-playbook -i development -i qa playbook.yml
Another way, but I have to say that I find this a overly complicated scenario, is to use the ansible_inventory_sources special variable, then align the name of your inventory files with the host groups:
## this is development inventory
[development]
node1.dev.example.org
node2.dev.example.org
## this is qa inventory
[qa]
node1.qa.example.org
node2.qa.example.org
So the playbook will have an horrible:
- hosts: "{{ ansible_inventory_sources | map('basename') | map('regex_replace', '^([^\\.]*).*', '\\1') | list | join(':') }}"
Where
basename would get the file name, e.g. inventory.yml
The regex_replace, is not really needed in your use case, but is borrowed from this answer by #Zeitounator, and allows to remove any file extension, if you do have inventory file extension, like development.yml. And since my inventories do have the .yml extension, this is worth noting.
the list is then join'ed with a colon (:) to fall back on your other question
And so running it, again with
ansible-playbook -i development -i qa playbook.yml
Will generate the host group pattern development:qa.
Here is a demo of what this give (using debug for the sake of the demo, but, as variables can be used in host too, this is the same):
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: "{{ ansible_inventory_sources | map('basename') | map('regex_replace', '^([^\\.]*).*', '\\1') | list | join(':') }}"
Give the recap:
PLAY [localhost] **************************************************************************************************
TASK [debug] ******************************************************************************************************
ok: [localhost] => {
"msg": "developement:qa"
}
PLAY RECAP ********************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
My question is somehow similar to the one posted here, but that doesn't quite answer it.
In my case I have an array containing multiple vars: entries, which I loop over when calling a certain role. The following examples shows the idea:
some_vars_file.yml:
redis_config:
- vars:
redis_version: 6.0.6
redis_port: 6379
redis_bind: 127.0.0.1
redis_databases: 1
- vars:
redis_version: 6.0.6
redis_port: 6380
redis_bind: 127.0.0.1
redis_databases: 1
playbook.yml:
...
- name: Install and setup redis
include_role:
name: davidwittman.redis
with_dict: "{{ dictionary }}"
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
...
As far as I understand, this should just set the dictionary beginning with the vars node on every iteration, but it somehow doesn't. Is there any chance to get something like this to work, or do I really have to redefine all properties at the role call, populating them using with_items?
Given the role
shell> cat roles/davidwittman_redis/tasks/main.yml
- debug:
var: dictionary
Remove with_dict. The playbook
shell> cat playbook.yml
- hosts: localhost
vars_files:
- some_vars_file.yml
tasks:
- name: Install and setup redis
include_role:
name: davidwittman_redis
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
gives
shell> ansible-playbook playbook.yml
PLAY [localhost] **********************************************
TASK [Install and setup redis] ********************************
TASK [davidwittman_redis : debug] *****************************
ok: [localhost] =>
dictionary:
vars:
redis_bind: 127.0.0.1
redis_databases: 1
redis_port: 6379
redis_version: 6.0.6
TASK [davidwittman_redis : debug] ******************************
ok: [localhost] =>
dictionary:
vars:
redis_bind: 127.0.0.1
redis_databases: 1
redis_port: 6380
redis_version: 6.0.6
Q: "Might there be an issue related to the variable population on role call?"
A: Yes. It can. See Variable precedence. vars_files is precedence 14. Any higher precedence will override it. Decide how to structure the data and optionally use include_vars (precedence 18). For example
shell> cat playbook.yml
- hosts: localhost
tasks:
- include_vars: some_vars_file.yml
- name: Install and setup redis
include_role:
name: davidwittman_redis
loop: "{{ redis_config }}"
loop_control:
loop_var: dictionary
Ultimately, command line --extra-vars would override all previous settings
shell> ansible-playbook playbook.yml --extra-vars "#some_vars_file.yml"
Q: "Maybe it is not possible to set the vars section directly via an external dictionary?"
A: It is possible, of course. The example in this answer clearly proves it.