使用Ansible set_fact从寄存器结果创建字典

Using Ansible set_fact to create a dictionary from register results

在ansible中,我使用register将任务的结果保存在变量people中。省略了我不需要的东西,它有这样的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
   "results": [
        {
           "item": {
               "name":"Bob"
            },
           "stdout":"male"
        },
        {
           "item": {
               "name":"Thelma"
            },
           "stdout":"female"
        }
    ]
}

我想使用后续的set_fact任务生成一个新的变量,使用如下字典:

1
2
3
4
{
   "Bob":"male",
   "Thelma":"female"
}

我想这可能是有可能的,但到目前为止,我已经走投无路了。


我想我终于到了那里。

任务如下:

1
2
3
4
- name: Populate genders
  set_fact:
    genders:"{{ genders|default({}) | combine( {item.item.name: item.stdout} ) }}"
  with_items:"{{ people.results }}"

它循环遍历people.results数组中的每个dict(item,每次创建一个新的dict,如{Bob:"male"}combine()genders数组中的新dict,其结果如下:

1
2
3
4
{
   "Bob":"male",
   "Thelma":"female"
}

它假定键(本例中的name键)是唯一的。

然后我意识到我实际上想要一个字典列表,因为使用with_items进行循环似乎更容易:

1
2
3
4
- name: Populate genders
  set_fact:
    genders:"{{ genders|default([]) + [ {'name': item.item.name, 'gender': item.stdout} ] }}"
  with_items:"{{ people.results }}"

这会不断地将现有列表与包含单个dict的列表组合在一起。我们最终得到一个这样的genders数组:

1
2
3
4
[
    {'name': 'Bob', 'gender': 'male'},
    {'name': 'Thelma', 'gender': 'female'}
]


感谢Phil提供的解决方案;如果有人遇到与我相同的情况,这里有一个(更复杂的)变体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
# this is just to avoid a call to |default on each iteration
- set_fact:
    postconf_d: {}

- name: 'get postfix default configuration'
  command: 'postconf -d'
  register: command

# the answer of the command give a list of lines such as:
#"key = value" or"key =" when the value is null
- name: 'set postfix default configuration as fact'
  set_fact:
    postconf_d: >
      {{
        postconf_d |
        combine(
          dict([ item.partition('=')[::2]|map('trim') ])
        )
  with_items: command.stdout_lines

这将提供以下输出(例如剥离):

1
2
3
4
5
6
"postconf_d": {
   "alias_database":"hash:/etc/aliases",
   "alias_maps":"hash:/etc/aliases, nis:mail.aliases",
   "allow_min_user":"no",
   "allow_percent_hack":"yes"
}

更进一步,分析"value"中的列表:

1
2
3
4
5
6
7
8
9
10
- name: 'set postfix default configuration as fact'
  set_fact:
    postconf_d: >-
      {% set key, val = item.partition('=')[::2]|map('trim') -%}
      {% if ',' in val -%}
        {% set val = val.split(',')|map('trim')|list -%}
      {% endif -%}
      {{ postfix_default_main_cf | combine({key: val}) }}
  with_items: command.stdout_lines
...
1
2
3
4
5
6
7
8
9
"postconf_d": {
   "alias_database":"hash:/etc/aliases",
   "alias_maps": [
       "hash:/etc/aliases",
       "nis:mail.aliases"
    ],
   "allow_min_user":"no",
   "allow_percent_hack":"yes"
}

需要注意的几点:

  • 在这种情况下,需要"修剪"一切(使用yaml中的>-和jinja中的-%}),否则会出现如下错误:

    1
    FAILED! => {"failed": true,"msg":"|combine expects dictionaries, got u"  {u'...
  • 显然,{% if ..远不是防弹的。

  • 在后缀的情况下,val.split(',')|map('trim')|list可以简化为val.split(', '),但我想指出的是,你需要|list否则你会得到如下错误:

    1
    "|combine expects dictionaries, got u"{u'...': <generator object do_map at ...

希望这能有所帮助。