英文:
Append a dict to a list of dicts in Ansible based on user configuration
问题
以下是您提供的文本的翻译:
我想通过Ansible部署一个应用程序,该应用程序可以通过systemd服务以可配置的应用节点数运行。我有一个管理这些服务的角色,但它需要服务的名称以及优先级等参数。
在我的playbook中,用户定义了类似于以下内容的内容:
application_nodes:
- name: "node1"
api_port: 3900
- name: "node2"
api_port: 3910
因此,我需要追加此systemd角色管理的服务列表。我尝试了以下方式:
service_list: "{% for node in application_nodes %}{'name': 'application_{{ node.name }}.service', 'priority': 1000}{% endfor %}"
devture_systemd_service_manager_services_list_auto: |
{{
([{{ service_list }}])
}}
这导致了以下错误:
FAILED! => {"msg": "在模板化 '{{\n ([{{ service_list }}])\n}}\n' 时发生了未处理的异常。错误是 <class 'ansible.errors.AnsibleError'>,原始消息是:在模板化字符串时出现模板错误: 预期的标记 ':',得到 '}'。字符串: {{\n ([{{ service_list }}])\n}}。预期的标记 ':',得到 '}'"}
我无法弄清楚我在这里做错了什么。
最终应该像这样工作(自动适用于每个节点):
devture_systemd_service_manager_services_list_auto: |
{{
([{'name': 'application_node1.service', 'priority': 1000}])
+
([{'name': 'application_node2.service', 'priority': 1000}])
}}
我还考虑过类似于以下方式:
- name: 追加服务列表以按节点
set_fact:
devture_systemd_service_manager_services_list_auto: "{{ devture_systemd_service_manager_services_list_auto + [{'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}] }}"
loop: "{{ application_nodes }}"
- name: "打印服务"
debug:
msg: "services {{ devture_systemd_service_manager_services_list_auto }}"
其中错误类似于这个问题,但该解决方案对我来说不起作用,因为我想访问字典的特定键:
TASK [custom/base : Print services B]
fatal: [mydomain]: FAILED! => {"msg": "该任务包含一个未定义变量的选项。错误是:[{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' 未定义。'item' 未定义。[{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' 未定义。'item' 未定义
错误似乎出现在文件 '/.../roles/custom/base/tasks/add_services.yml' 中:第11行,第3列,但根据确切的语法问题,它也可能出现在文件的其他位置。
有问题的行似乎是:
- name: "打印服务 B"
^ 在这里
"}
英文:
I want to deploy an application via ansible that has a configurable number of application nodes running via a systemd service. I have a role that manages these services for me but expects the name of the services along with e.g. the priority.
In my playbook the user defines something like this
application_nodes:
- name: "node1"
api_port: 3900
- name: "node2"
api_port: 3910
Therefore I need to append the list that this the systemd role manages. I tried the following
service_list: "{% for node in application_nodes %}{'name': 'application_{{ node.name }}.service', 'priority': 1000}{% endfor %}"
devture_systemd_service_manager_services_list_auto: |
{{
([{{ service_list }}])
}}
This results in the following error
FAILED! => {"msg": "An unhandled exception occurred while templating '{{\n ([{{ service_list }}])\n}}\n'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: expected token ':', got '}'. String: {{\n ([{{ service_list }}])\n}}\n. expected token ':', got '}'"}
I cannot figure out what I did wrong here.
I the end it should work like this (except automatically for every node)
devture_systemd_service_manager_services_list_auto: |
{{
([{'name': 'application_node1.service', 'priority': 1000}])
+
([{'name': 'application_node2.service', 'priority': 1000}])
}}
I also considered something like this
- name: Append services list by nodes
set_fact:
devture_systemd_service_manager_services_list_auto: "{{ devture_systemd_service_manager_services_list_auto + [{'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}] }}"
loop: "{{ application_nodes }}"
- name: "Print services"
debug:
msg: "services {{ devture_systemd_service_manager_services_list_auto }}"
Where the error is similar to this issue but the solution does not work for me as I want to access a specific key of the dictionary
TASK [custom/base : Print services B] ********************************************************************************************************************************************************************************************************************
fatal: [mydomain]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: [{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' is undefined. 'item' is undefined. [{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' is undefined. 'item' is undefined\n\nThe error appears to be in '/.../roles/custom/base/tasks/add_services.yml': line 11, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: \"Print services B\"\n ^ here\n"}
答案1
得分: 2
首先,你所使用的角色中的变量定义对于一个简单的字典列表来说过于复杂,这会导致混淆。所以,为了明确起见,以下表达式:
devture_systemd_service_manager_services_list_auto: |
{{
([{'name': 'application_node1.service', 'priority': 1000}])
+
([{'name': 'application_node2.service', 'priority': 1000}])
}}
实际上等同于:
devture_systemd_service_manager_services_list_auto:
- name: application_node1.service
priority: 1000
- name: application_node2.service
priority: 1000
其次,我认为第二个混淆源是你使用了 append
这个术语(在Ansible中是向列表添加更多元素的操作),而不是 combine
(即从另一个字典中添加/更新键)。
有了这些澄清,我们可以满足你的需求:
- 从原始列表中提取每个元素的
name
属性 - 转换该名称以使其与你期望的服务名称匹配
- 将包含该名称和期望优先级的字典附加到结果列表的相关键中
请注意,必须转换名称,这使得很难在变量定义的模板中避免循环(在Ansible中我们通常尽量避免这样做)。但这仍然可以在一个表达式中完成,如下面测试剧本所示:
---
- hosts: localhost
gather_facts: false
vars:
application_nodes:
- name: "node1"
api_port: 3900
- name: "node2"
api_port: 3910
devture_systemd_service_manager_services_list_auto: >-
{%- set result = [] -%}
{%- for node in application_nodes -%}
{%- set service_name = node.name | regex_replace('^(.*)$', 'application_\.service') -%}
{{ result.append({'name': service_name, 'priority': 1000}) }}
{%- endfor -%}
{{ result }}
tasks:
- name: Show our list
ansible.builtin.debug:
var: devture_systemd_service_manager_services_list_auto
这将产生(仅显示相关任务输出):
TASK [Show our list] *********************************************************
ok: [localhost] => {
"devture_systemd_service_manager_services_list_auto": [
{
"name": "application_node1.service",
"priority": 1000
},
{
"name": "application_node2.service",
"priority": 1000
}
]
}
英文:
To start with, the var definition in the role your are using is overly complicated for something as simple as a list of dicts. This is a source of confusion. So to be clear the following expression:
devture_systemd_service_manager_services_list_auto: |
{{
([{'name': 'application_node1.service', 'priority': 1000}])
+
([{'name': 'application_node2.service', 'priority': 1000}])
}}
is actually the exact equivalent of:
devture_systemd_service_manager_services_list_auto:
- name: application_node1.service
priority: 1000
- name: application_node2.service
priority: 1000
The second source of confusion IMO is that you used the term append
(which is in Ansible the fact to add more elements to a list) rather than combine
(i.e. adding/updating keys in one dict from an other dict).
Those precisions being made, we can fulfill your requirement by:
- Extracting the
name
attribute from each element in the original list - Transform that name so that it matches your expected service name
- Append a dict containing that name and the expected priority in relevant keys to a result list
Note that having to transform the name makes it difficult to avoid looping inside a template for the var definition (which we generally tend to avoid in Ansible). But this can still be done in one single expression as demonstrated in the following test playbook:
---
- hosts: localhost
gather_facts: false
vars:
application_nodes:
- name: "node1"
api_port: 3900
- name: "node2"
api_port: 3910
devture_systemd_service_manager_services_list_auto: >-
{%- set result = [] -%}
{%- for node in application_nodes -%}
{%- set service_name = node.name | regex_replace('^(.*)$', 'application_\.service') -%}
{{ result.append({'name': service_name, 'priority': 1000}) }}
{%- endfor -%}
{{ result }}
tasks:
- name: Show our list
ansible.builtin.debug:
var: devture_systemd_service_manager_services_list_auto
which gives (relevant task output only)
TASK [Show our list] *********************************************************
ok: [localhost] => {
"devture_systemd_service_manager_services_list_auto": [
{
"name": "application_node1.service",
"priority": 1000
},
{
"name": "application_node2.service",
"priority": 1000
}
]
}
答案2
得分: 1
- 主机:全部
变量:
应用节点:
- 名称:“node1”
API端口:3900
- 名称:“node2”
API端口:3910
任务:
- 设置事实:
# 循环前后的方括号使其成为实际列表。
# 为确保列表项与彼此分开,我们必须在每个项后面添加逗号。幸运的是,Ansible / Python 对没有最终项的逗号不太挑剔。;-)
服务列表: >
[
{% for node in application_nodes %}
{'name': 'application_{{ node.name }}.service', 'priority': 1000},
{% endfor %}
]
- 设置事实:
devture_systemd_service_manager_services_list_auto: >
{{ devture_systemd_service_manager_services_list_auto|default([]) +
service_list }}
- 调试:
变量:devture_systemd_service_manager_services_list_auto
英文:
You almost had it in your first try. If you add a few things to your service_list
it ends up being a list of dicts -- which is ultimately what you want to append to devture_systemd_service_manager_services_list_auto
.
- hosts: all
vars:
application_nodes:
- name: "node1"
api_port: 3900
- name: "node2"
api_port: 3910
tasks:
- set_fact:
# The square brackets before and after the loop make this an actual
# list.
# To make sure that the list items are separated from each other, we
# have to add a comma after each item. Luckily Ansible / Python isn't
# too picky about a comma without a final item. ;-)
service_list: >
[
{% for node in application_nodes %}
{'name': 'application_{{ node.name }}.service', 'priority': 1000},
{% endfor %}"
]
- set_fact:
devture_systemd_service_manager_services_list_auto: >
{{ devture_systemd_service_manager_services_list_auto|default([]) +
service_list }}
- debug:
var: devture_systemd_service_manager_services_list_auto
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论