Ansible. 重写单个字典键

40 投票
4 回答
30363 浏览
提问于 2025-04-18 16:00

我正在使用ansible来管理生产环境的配置,还有vagrant箱子。
我有一个包含默认值的文件:group_vars/all

---
env: prod
wwwuser: www-data

db:
    root_pwd: root_pwd
    pdo_driver: pdo_mysql
    host: localhost
    name: test
    user: test
    pwd: test
    charset: utf8

domain: somedomain
projectdir: /var/www/application
webrootdir: "{{ projectdir }}/web"

host_vars/vagrantbox中,我想要有类似这样的内容:

db:
    root_pwd: super_easy_password

但是这样做会完全覆盖掉数据库的字典,而我只想覆盖其中的一个键。
我该怎么做才能实现这个目标呢?

更新 1
我刚刚检查了ansible.cfg:

[defaults]
host_key_checking=false
hash_behaviour=merge

groups_vars/all

db:
    root_pwd: some_strong_pwd
    pdo_driver: pdo_mysql
    host: localhost
    name: dbname
    user: dbuser
    pwd: some password
    charset: utf8

host_vars/vagrantbox

db:
    root_pwd: root

我遇到了以下错误:

One or more undefined variables: 'dict object' has no attribute 'name'

我哪里做错了呢?

4 个回答

-1

我刚刚用 ansible 1.9.3 试了一下,运行得很好。不太确定,但看起来你在 group_vars 这个文件夹的名字上写错了(应该是 group_vars,而不是 groups_vars)。

9

我发现最好的方法是把变量当作字典项的值,然后覆盖它。这样做可以让变量的优先级变得简单而强大,特别是在处理ansible的变量顺序时。

角色/父级/默认/main.yml

---
root_pw_value: ParentPassword

parent_dict:
  - root_pw: "{{ root_pw_value }}"

角色/子级/默认/main.yml

注意:角色/子级/meta/main.yml包含 dependencies: - { role: parent }

---
root_pw_value: ChildPassword

play-me.yml

---
  - hosts: all
    roles:
      - child

角色/父级/任务/main.yml & 角色/子级/任务/main.yml

- debug: var=parent_dict

运行 ansible -i localhost, --connection="local" play-me.yml,你会看到以下输出:

PLAY [all] ******************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [localhost]

TASK: [parent | debug var=parent_dict] **************************************** 
ok: [localhost] => {
    "var": {
        "parent_dict": [
            {
                "root_pw": "ParentPassword"
            }
        ]
    }
}

TASK: [child | debug var=parent_dict] ***************************************** 
ok: [localhost] => {
    "var": {
        "parent_dict": [
            {
                "root_pw": "ChildPassword"
            }
        ]
    }
}

PLAY RECAP ******************************************************************** 
localhost                  : ok=3    changed=0    unreachable=0    failed=0

这些都是默认值。如果你在更具体的优先级层级上指定 root_pw_value,比如在清单组/主机变量、角色变量、命令行的额外变量,或者任何来自优先级顺序的东西,你就会得到那些值。

[0] http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable

17

从Ansible 2.0开始,你可以使用Jinja2的combine过滤器来合并YAML的哈希表或字典,而不需要在你的ansible.cfg文件中全局设置hash_behavior=merge

相关文档:https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#sts=Combining%20hashes/dictionaries%C2%B6

32

默认情况下,Ansible会在第一级覆盖变量。如果你想要合并字典(就是把两个字典的内容合在一起),你需要修改你的 ansible.cfg 文件,并设置:

hash_behaviour=merge

(默认值是 replace)。

需要注意的是,Ansible团队 并不推荐这样做。我觉得这在用户之间是个很大的分歧。这是一个一劳永逸的决定:一旦你开始使用这个功能,就不能再回头了,而且你可能也无法和那些使用 replace 的人共享你的剧本。

不过,你仍然可以从网上找到的剧本中受益(我觉得剧本并不会把 replace 行为当成“功能”来使用)。这就像是AB型血的人,能接受任何血型……但因为魔法通常发生在变量解析的过程中,而不是在任务或模板内部,我认为通常可以在不做任何修改的情况下共享你的角色。

如果你需要覆盖某个特定角色的参数中的单个键,你就得用一些复杂的方法来传递参数。

例如,要在一个 php5 字典中覆盖 post_max_sizeupload_max_size 这两个键,你需要这样做:

- { role: php5-fpm, php5: { post_max_size: 40M,
                            upload_max_filesize: 20M }}

话虽如此,我从一开始就使用 merge 行为,并且对此非常满意。这样可以很好地保持变量的组织性。

撰写回答