基于其他主机的变量构建模板。

huangapple go评论69阅读模式
英文:

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 is hostvars
  • the filter selectattr, twice, to filter out any host(s) where my.info would not be defined, then, filter out other hosts that do not share the same info.name as the current host
  • a for ... if ... construct to reject any host not having the same info[].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&gt; cat hosts
host_1
host_2
host_3
host_4

and the host_vars

shell&gt; cat host_vars/host_1/my_info.yml 
my:
  info:
    - name: one
    - name: two
shell&gt; cat host_vars/host_2/my_info.yml 
my:
  info:
    - name: two
    - name: three
shell&gt; cat host_vars/host_3/my_info.yml 
my:
  info:
    - name: one
    - name: three
shell&gt; 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(&#39; &#39;) }}
{% 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: &quot;{{ my.info|product([{&#39;hosts&#39;: [inventory_hostname]}])|
                           map(&#39;combine&#39;) }}&quot;
  - debug:
      var: my_info|to_yaml

gives

TASK [debug] ************************************************
ok: [host_2] =&gt; 
  my_info:
    - hosts: [host_2]
      name: two
    - hosts: [host_2]
      name: three
ok: [host_1] =&gt; 
  my_info:
    - hosts: [host_1]
      name: one
    - hosts: [host_1]
      name: two
ok: [host_4] =&gt; 
  my_info:
    - hosts: [host_4]
      name: whatever
ok: [host_3] =&gt; 
  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: &quot;{{ ansible_play_hosts_all|
                  map(&#39;extract&#39;, hostvars, &#39;my_info&#39;)|
                  community.general.lists_mergeby(&#39;name&#39;, list_merge=&#39;append&#39;)|
                  items2dict(key_name=&#39;name&#39;, value_name=&#39;hosts&#39;) }}&quot;

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(&#39; &#39;) }}
        {% endfor %}        

gives

TASK [debug] ************************************************
ok: [host_1] =&gt; 
  msg: |-
    one host_1 host_3
    two host_1 host_2    
ok: [host_3] =&gt; 
  msg: |-
    one host_1 host_3
    three host_2 host_3    
ok: [host_2] =&gt; 
  msg: |-
    two host_1 host_2
    three host_2 host_3    
ok: [host_4] =&gt; 
  msg: |-
        whatever host_4

<sup>

Example of a complete playbook for testing

- hosts: all

  vars:

    name_hosts: &quot;{{ ansible_play_hosts_all|
                    map(&#39;extract&#39;, hostvars, &#39;my_info&#39;)|
                    community.general.lists_mergeby(&#39;name&#39;, list_merge=&#39;append&#39;)|
                    items2dict(key_name=&#39;name&#39;, value_name=&#39;hosts&#39;) }}&quot;

  tasks:

    - set_fact:
        my_info: &quot;{{ my.info|product([{&#39;hosts&#39;: [inventory_hostname]}])|
                             map(&#39;combine&#39;) }}&quot;
    - 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(&#39; &#39;) }}
          {% endfor %}          

</sup>

<hr>

Example 2.

Create a dictionary of all my.info

  all_info: &quot;{{ dict(ansible_play_hosts_all|
                     zip(ansible_play_hosts_all|
                         map(&#39;extract&#39;, hostvars, [&#39;my&#39;, &#39;info&#39;])|
                         map(&#39;map&#39;, attribute=&#39;name&#39;))) }}&quot;

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: &quot;{{ all_info.values()|flatten|unique }}&quot;

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(&#39;value&#39;, &#39;contains&#39;, name)|map(attribute=&#39;key&#39;) }}
    {% 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() %}
              {{ &#39;#&#39; }} for {{ host }}
              {% for name in names %}
              {{ name }} {{ name_hosts[name]|join(&#39; &#39;) }}
              {% 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(&#39; &#39;) }}
          {% endfor %}          

gives

TASK [debug] *****************************************
ok: [host_4] =&gt; 
  msg: |-
        whatever host_4
ok: [host_1] =&gt; 
  msg: |-
    one host_1 host_3
    two host_1 host_2    
ok: [host_2] =&gt; 
  msg: |-
    two host_1 host_2
    three host_2 host_3    
ok: [host_3] =&gt; 
  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: &quot;{{ dict(ansible_play_hosts_all|
                       zip(ansible_play_hosts_all|
                           map(&#39;extract&#39;, hostvars, [&#39;my&#39;, &#39;info&#39;])|
                           map(&#39;map&#39;, attribute=&#39;name&#39;))) }}&quot;
    all_names: &quot;{{ all_info.values()|flatten|unique }}&quot;
    names_hosts: |
      {% filter from_yaml %}
      {% for tag in all_names %}
      {{ tag }}: {{ all_info|dict2items|selectattr(&#39;value&#39;, &#39;contains&#39;, tag)|map(attribute=&#39;key&#39;) }}
      {% 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() %}
              {{ &#39;#&#39; }} for {{ host }}
              {% for name in names %}
              {{ name }} {{ names_hosts[name]|join(&#39; &#39;) }}
              {% endfor %}

              {% endfor %}              
      run_once: true

    - debug:
        msg: |
          {% for i in my.info %}
          {{ i.name }} {{ names_hosts[i.name]|join(&#39; &#39;) }}
          {% endfor %}          

</sup>

huangapple
  • 本文由 发表于 2023年6月29日 17:51:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76579942.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定