英文:
Build template based on variables of other hosts
问题
我想要构建一个模板,每个主机都有不同的内容。
假设有以下四个主机和它们的变量:
# 主机1的变量
my:
info:
- name: one
- name: two
# 主机2的变量
my:
info:
- name: two
- name: three
# 主机3的变量
my:
info:
- name: one
- name: three
# 主机4的变量
my:
info:
- name: whatever
模板的结果应该是:
# 对于主机1
one 主机1 主机3
two 主机1 主机2
# 对于主机2
two 主机2 主机1
three 主机2 主机3
# 对于主机3
one 主机3 主机1
three 主机3 主机2
# 对于主机4
whatever 主机4
我的问题是:我想保持配置简单。我应该在哪里构建这个逻辑?我应该在Ansible运行时在主机之间共享事实,然后进行模板化吗?还是已经有一种在模板化过程中轻松构建这个逻辑的解决方案?
英文:
I want to build a template which differs for each host.
Assume these four hosts with the following variables:
# host_1 host_vars
my:
info:
- name: one
- name: two
# host_2 host_vars
my:
info:
- name: two
- name: three
# host_3 host_vars
my:
info:
- name: one
- name: three
# host_4 host_vars
my:
info:
- name: whatever
The result of the template should be:
# for host_1
one host_1 host_3
two host_1 host_2
# for host_2
two host_2 host_1
three host_2 host_3
# for host_3
one host_3 host_1
three host_3 host_2
# for host_4
whatever host_4
My question is: I want to keep the configuration simple.
Where should I build this logic?
Should I share facts among hosts during Ansible runtime and then template it? Or is there a solution to build this easily during templating already?
答案1
得分: 2
您的描述有点抽象,我不太确定我完全理解您的全部需求。但对我来说,看起来您正在重新发明轮子。
如果我对上面的描述重新表述一下:您有属于不同组的主机,并且您想要一个模板,对于每个主机所属的组,都会写一行,包括组的名称,然后是当前主机,然后是所有其他成员。
以下是一个实现这个需求的最小示例。
文件结构:
$ tree
.
├── inventories
│ └── default
│ └── hosts.yml
├── playbook.yml
└── templates
└── my_template.j2
3 directories, 3 files
我们的测试inventories/default/hosts.yml
清单:
---
one:
hosts:
host_1:
host_3:
two:
hosts:
host_1:
host_2:
three:
hosts:
host_2:
host_3:
whatever:
hosts:
host_4:
templates/my_template.j2
的内容是:
{% for current_group in group_names %}
{% set other_hosts = groups[current_group] | difference([inventory_hostname]) %}
{{ current_group }} {{ inventory_hostname }} {{ other_hosts | join(' ') }}
{% endfor %}
以及用于测试该模板的playbook.yml
:
---
- hosts: all
# 仅用于测试
connection: local
gather_facts: false
tasks:
- name: Show result of template
vars:
template_result: "{{ lookup('template', 'my_template.j2') }}"
to_debug_result: "{{ template_result | split('\n') }}"
ansible.builtin.debug:
var: to_debug_result
结果如下:
$ ansible-playbook -i inventories/default/ playbook.yml
PLAY [all] ************************************************************************************************************************************************************************************************
TASK [Show result of template] ****************************************************************************************************************************************************************************
ok: [host_1] => {
"to_debug_result": [
"one host_1 host_3",
"two host_1 host_2",
""
]
}
ok: [host_3] => {
"to_debug_result": [
"one host_3 host_1",
"three host_3 host_2",
""
]
}
ok: [host_2] => {
"to_debug_result": [
"three host_2 host_3",
"two host_2 host_1",
""
]
}
ok: [host_4] => {
"to_debug_result": [
"whatever host_4 ",
""
]
}
PLAY RECAP ************************************************************************************************************************************************************************************************
host_1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
英文:
Your description is a bit abstract and I'm not totally sure I get your full requirement. But to me it looks like you are reinventing the wheel.
If I rephrase the above as I get it: you have hosts belonging to groups and you want a template that will, for each group the host belongs to, write a line with the name of the group followed by the current host and then all other members.
Here is a minimal example which achieves that requirement
File structure:
$ tree
.
├── inventories
│   └── default
│   └── hosts.yml
├── playbook.yml
└── templates
└── my_template.j2
3 directories, 3 files
Our test inventories/default/hosts.yml
inventory:
---
one:
hosts:
host_1:
host_3:
two:
hosts:
host_1:
host_2:
three:
hosts:
host_2:
host_3:
whatever:
hosts:
host_4:
The content of templates/my_template.j2
is:
{% for current_group in group_names %}
{% set other_hosts = groups[current_group] | difference([inventory_hostname]) %}
{{ current_group }} {{ inventory_hostname }} {{ other_hosts | join(' ') }}
{% endfor %}
And the playbook.yml
to test that template:
---
- hosts: all
# This is for test only
connection: local
gather_facts: false
tasks:
- name: Show result of template
vars:
template_result: "{{ lookup('template', 'my_template.j2') }}"
to_debug_result: "{{ template_result | split('\n') }}"
ansible.builtin.debug:
var: to_debug_result
gives as a result:
$ ansible-playbook -i inventories/default/ playbook.yml
PLAY [all] ************************************************************************************************************************************************************************************************
TASK [Show result of template] ****************************************************************************************************************************************************************************
ok: [host_1] => {
"to_debug_result": [
"one host_1 host_3",
"two host_1 host_2",
""
]
}
ok: [host_3] => {
"to_debug_result": [
"one host_3 host_1",
"three host_3 host_2",
""
]
}
ok: [host_2] => {
"to_debug_result": [
"three host_2 host_3",
"two host_2 host_1",
""
]
}
ok: [host_4] => {
"to_debug_result": [
"whatever host_4 ",
""
]
}
PLAY RECAP ************************************************************************************************************************************************************************************************
host_1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
答案2
得分: 2
以下是您要翻译的内容:
"由于您可以使用特殊变量hostvars
访问其他主机的变量,因此您肯定可以模板化某些变量也存在于另一个主机变量中的事实。
一个执行此操作的模板示例如下:
{% for info in my.info %}
{{- info.name -}}
{% for host in hostvars
| dict2items
| selectattr('value.my.info', 'defined')
if host.value.my.info
| selectattr('name', '==', info.name)
%} {{ host.key }}{% endfor %}
{% endfor %}
在此示例中,我们使用了:
- 过滤器
dict2items
,将主机字典的列表从hostvars
主机字典中生成 - 两次使用了
selectattr
过滤器,以过滤出未定义my.info
的任何主机,然后过滤出与当前主机不共享相同info.name
的其他主机 - 使用
for ... if ...
结构,拒绝任何主机,其info[].name
与当前主机不同
例如,使用以下清单:
all:
children:
demo:
hosts:
host_1:
ansible_host: ansible-node-1
my:
info:
- name: one
- name: two
host_2:
ansible_host: ansible-node-2
my:
info:
- name: two
- name: three
host_3:
ansible_host: ansible-node-3
my:
info:
- name: one
- name: three
host_4:
ansible_host: ansible-node-4
my:
info:
- name: whatever
和以下的 debug
任务:
- debug:
msg: >-
{% for info in my.info %}
{{- info.name -}}
{% for host in hostvars
| dict2items
| selectattr('value.my.info', 'defined')
if host.value.my.info
| selectattr('name', '==', info.name)
%} {{ host.key }}{% endfor %}
{% endfor %}
我们会得到以下结果:
ok: [host_4] =>
msg: |-
whatever host_4
ok: [host_1] =>
msg: |-
one host_1 host_3
two host_1 host_2
ok: [host_2] =>
msg: |-
two host_1 host_2
three host_2 host_3
ok: [host_3] =>
msg: |-
one host_1 host_3
three host_2 host_3
英文:
Since you can access the variables of other hosts with the help of the special variable hostvars
, you can definitely template the fact that some variables are also present in another host variables.
An example of template doing so would be:
{% for info in my.info %}
{{- info.name -}}
{% for host in hostvars
| dict2items
| selectattr('value.my.info', 'defined')
if host.value.my.info
| selectattr('name', '==', info.name)
%} {{ host.key }}{% endfor %}
{% endfor %}
In this example we are using:
- the filter
dict2items
, to make a list of hosts dictionaries out of the dictionary of hosts that ishostvars
- the filter
selectattr
, twice, to filter out any host(s) wheremy.info
would not be defined, then, filter out other hosts that do not share the sameinfo.name
as the current host - a
for ... if ...
construct to reject any host not having the sameinfo[].name
as the current host
For example, with the inventory:
all:
children:
demo:
hosts:
host_1:
ansible_host: ansible-node-1
my:
info:
- name: one
- name: two
host_2:
ansible_host: ansible-node-2
my:
info:
- name: two
- name: three
host_3:
ansible_host: ansible-node-3
my:
info:
- name: one
- name: three
host_4:
ansible_host: ansible-node-4
my:
info:
- name: whatever
And the debug
task:
- debug:
msg: >-
{% for info in my.info %}
{{- info.name -}}
{% for host in hostvars
| dict2items
| selectattr('value.my.info', 'defined')
if host.value.my.info
| selectattr('name', '==', info.name)
%} {{ host.key }}{% endfor %}
{% endfor %}
We get a result of:
ok: [host_4] =>
msg: |-
whatever host_4
ok: [host_1] =>
msg: |-
one host_1 host_3
two host_1 host_2
ok: [host_2] =>
msg: |-
two host_1 host_2
three host_2 host_3
ok: [host_3] =>
msg: |-
one host_1 host_3
three host_2 host_3
答案3
得分: 1
Short answer: 创建以下字典
name_hosts:
one: [host_1, host_3]
three: [host_2, host_3]
two: [host_1, host_2]
whatever: [host_4]
然后,模板如下
{% for i in my.info %}
{{ i.name }} {{ name_hosts[i.name]|join(' ') }}
{% endfor %}
如果需要更多细节,请查看原始文本。
英文:
<sup>
Given the inventory
shell> cat hosts
host_1
host_2
host_3
host_4
and the host_vars
shell> cat host_vars/host_1/my_info.yml
my:
info:
- name: one
- name: two
shell> cat host_vars/host_2/my_info.yml
my:
info:
- name: two
- name: three
shell> cat host_vars/host_3/my_info.yml
my:
info:
- name: one
- name: three
shell> cat host_vars/host_4/my_info.yml
my:
info:
- name: whatever
</sup>
<hr>
Short answer: Create the dictionary
name_hosts:
one: [host_1, host_3]
three: [host_2, host_3]
two: [host_1, host_2]
whatever: [host_4]
Then, the template is trivial
{% for i in my.info %}
{{ i.name }} {{ name_hosts[i.name]|join(' ') }}
{% endfor %}
<hr>
Details: There are more options. Use Community.General in the first example. If you can't or don't want to use Community.General the second example provides solution with Ansible.Builtin functions only.
Example 1.
Instantiate lists of hosts and names my_info
- set_fact:
my_info: "{{ my.info|product([{'hosts': [inventory_hostname]}])|
map('combine') }}"
- debug:
var: my_info|to_yaml
gives
TASK [debug] ************************************************
ok: [host_2] =>
my_info:
- hosts: [host_2]
name: two
- hosts: [host_2]
name: three
ok: [host_1] =>
my_info:
- hosts: [host_1]
name: one
- hosts: [host_1]
name: two
ok: [host_4] =>
my_info:
- hosts: [host_4]
name: whatever
ok: [host_3] =>
my_info:
- hosts: [host_3]
name: one
- hosts: [host_3]
name: three
Extract all lists my_info and merge the items by name
name_hosts: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'my_info')|
community.general.lists_mergeby('name', list_merge='append')|
items2dict(key_name='name', value_name='hosts') }}"
gives
name_hosts:
one: [host_1, host_3]
three: [host_2, host_3]
two: [host_1, host_2]
whatever: [host_4]
Create the template
- debug:
msg: |
{% for i in my.info %}
{{ i.name }} {{ name_hosts[i.name]|join(' ') }}
{% endfor %}
gives
TASK [debug] ************************************************
ok: [host_1] =>
msg: |-
one host_1 host_3
two host_1 host_2
ok: [host_3] =>
msg: |-
one host_1 host_3
three host_2 host_3
ok: [host_2] =>
msg: |-
two host_1 host_2
three host_2 host_3
ok: [host_4] =>
msg: |-
whatever host_4
<sup>
Example of a complete playbook for testing
- hosts: all
vars:
name_hosts: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'my_info')|
community.general.lists_mergeby('name', list_merge='append')|
items2dict(key_name='name', value_name='hosts') }}"
tasks:
- set_fact:
my_info: "{{ my.info|product([{'hosts': [inventory_hostname]}])|
map('combine') }}"
- debug:
var: my_info|to_yaml
- debug:
var: name_hosts|to_yaml
run_once: true
- debug:
msg: |
{% for i in my.info %}
{{ i.name }} {{ name_hosts[i.name]|join(' ') }}
{% endfor %}
</sup>
<hr>
Example 2.
Create a dictionary of all my.info
all_info: "{{ dict(ansible_play_hosts_all|
zip(ansible_play_hosts_all|
map('extract', hostvars, ['my', 'info'])|
map('map', attribute='name'))) }}"
gives
all_info:
host_1: [one, two]
host_2: [two, three]
host_3: [one, three]
host_4: [whatever]
Create a list of all names
all_names: "{{ all_info.values()|flatten|unique }}"
gives
all_names: [one, two, three, whatever]
Create a dictionary of all names and hosts
name_hosts: |
{% filter from_yaml %}
{% for name in all_names %}
{{ name }}: {{ all_info|dict2items|selectattr('value', 'contains', name)|map(attribute='key') }}
{% endfor %}
{% endfilter %}
gives
name_hosts:
one: [host_1, host_3]
three: [host_2, host_3]
two: [host_1, host_2]
whatever: [host_4]
Now you've got all elements to create the template you want
- debug:
msg: |
{% for host,names in all_info.items() %}
{{ '#' }} for {{ host }}
{% for name in names %}
{{ name }} {{ name_hosts[name]|join(' ') }}
{% endfor %}
{% endfor %}
gives
msg: |-
# for host_1
one host_1 host_3
two host_1 host_2
# for host_2
two host_1 host_2
three host_2 host_3
# for host_3
one host_1 host_3
three host_2 host_3
# for host_4
whatever host_4
Optionally, each host can use the template
- debug:
msg: |
{% for i in my.info %}
{{ i.name }} {{ name_hosts[i.name]|join(' ') }}
{% endfor %}
gives
TASK [debug] *****************************************
ok: [host_4] =>
msg: |-
whatever host_4
ok: [host_1] =>
msg: |-
one host_1 host_3
two host_1 host_2
ok: [host_2] =>
msg: |-
two host_1 host_2
three host_2 host_3
ok: [host_3] =>
msg: |-
one host_1 host_3
three host_2 host_3
<hr>
<sup>
Example of a complete playbook for testing
- hosts: all
vars:
all_info: "{{ dict(ansible_play_hosts_all|
zip(ansible_play_hosts_all|
map('extract', hostvars, ['my', 'info'])|
map('map', attribute='name'))) }}"
all_names: "{{ all_info.values()|flatten|unique }}"
names_hosts: |
{% filter from_yaml %}
{% for tag in all_names %}
{{ tag }}: {{ all_info|dict2items|selectattr('value', 'contains', tag)|map(attribute='key') }}
{% endfor %}
{% endfilter %}
tasks:
- block:
- debug:
var: all_info|to_yaml
- debug:
var: all_names|to_yaml
- debug:
var: names_hosts|to_yaml
- debug:
msg: |
{% for host,names in all_info.items() %}
{{ '#' }} for {{ host }}
{% for name in names %}
{{ name }} {{ names_hosts[name]|join(' ') }}
{% endfor %}
{% endfor %}
run_once: true
- debug:
msg: |
{% for i in my.info %}
{{ i.name }} {{ names_hosts[i.name]|join(' ') }}
{% endfor %}
</sup>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论