【问题标题】:In Ansible, how to combine variables from separate files into one array?在 Ansible 中,如何将不同文件中的变量组合成一个数组?
【发布时间】:2022-03-25 21:41:41
【问题描述】:

在 Ansible 中,在一个角色中,我有这样的 vars 文件:

vars/
    app1.yml
    app2.yml

每个文件都包含特定于应用程序/网站的变量,如下所示:

name: app1
git_repo: https://github.com/philgyford/app1.git
# ...

理想情况下,如果任务事先不知道哪些应用程序具有可变文件,我希望最终得到一个名为 apps 的数组,如下所示:

apps:
  - name: app1
    git_repo: https://github.com/philgyford/app1.git
    # ...
  - name: app2
    git_repo: https://github.com/philgyford/app2.git
    # ...

即,将文件中的变量合并为一个。

我知道我可以像这样加载所有变量文件:

- name: Load var files
  with_fileglob:
    - ../vars/*.yml
  include_vars: '{{ item }}'

但如果每个文件都有相同的变量名,它将覆盖之前的每组变量。我看不到加载变量并将它们放入apps 数组的方法。

如果这是使这样的事情成为可能的唯一方法,我愿意稍微重新安排一些事情。

【问题讨论】:

    标签: ansible


    【解决方案1】:

    你不能那样做。变量将始终覆盖具有相同名称的变量。你唯一能做的就是write your own vars plugin,它会读取这些文件并将它们合并到一个数组中。

    如果您愿意更改应用定义的结构,您可以使用哈希和set your hash_behavior=merge。在每个 vars 文件中,您将有如下定义:

    apps:
      app1:
        git_repo: https://github.com/philgyford/app1.git
    

    apps:
      app2:
        git_repo: https://github.com/philgyford/app2.git
    

    当 Ansible 加载这两个文件时,它会自动将其合并到:

    apps:
      app1:
        git_repo: https://github.com/philgyford/app1.git
      app2:
        git_repo: https://github.com/philgyford/app2.git</pre>
    

    但请注意,hash_behavior=merge 从根本上改变了 Ansible 在全局级别的默认行为。确保您的所有角色都没有与此设置有关的问题。文档提到:

    我们通常建议不要使用此设置,除非您认为自己绝对需要它

    如果您仍然使用 Ansible 1,您可以改用我的旧插件之一:include_vars_merged。基本上这会将hash_behavior=merge 的行为添加到仅一个任务中。

    虽然我还没有考虑将它迁移到 Ansible 2,但目前看来我不再需要它了。

    【讨论】:

    • 在 Ansible 2 中,您可以使用 combine Jinja 过滤器,但它确实需要您知道要预先组合的哈希值
    • 我对这个过滤器寄予如此很大的希望,但不幸的是,您不能使用它来扩展散列并以相同的名称存储结果。 some_hash: "{{ some_hash | combine({foo: bar}) }}" 会很有用。
    • 谢谢两位。我担心没有简单的答案 - 我会找到其他一些不太好的方法来处理这些变量。我遇到过hash_behaviour=merge,但我很谨慎,因为它改变了 Ansible 的行为。 combine 也提出了我的希望,只是为了迅速打破它们。这么近……
    • 如果你从一个干净的剧本开始,你可以加载一组变量,然后从hostvars字典中将它们加载到你的目标字典中,过滤掉所有以ansible_开头的键, groupinventory_module_omitplaybook_。第二步,过滤掉你的目标字典。
    • 声明“你不能那样做......你唯一能做的就是编写你自己的 vars 插件来读取这些文件并将它们合并到一个数组中。”是错的。看我的回答。 ——
    【解决方案2】:

    嗯,你不能直接构建一个数组,但是你可以用一个字典来达到同样的效果。

    假设你要构造一个数组:

    [{
        name: 'bob',
        age: 30
    }, {
        name: 'alice',
        age: 35 
    }]
    

    您可以将每个元素放在一个文件中,例如:

    bob.yml

    bob:
      name: bob
      age: 30
    

    alice.yml

    alice:
      name: alice
      age: 35
    

    将这些文件放在同一个目录中(例如user),然后使用include_vars 加载整个目录:

    - name: Include vars
      include_vars:
        name: users
        dir: user
    

    这会给你一个字典users

    users:
      alice:
        name: alice
        age: 35
      bob:
        name: bob
        age: 30
    

    在ansible中使用dict2items过滤器,得到你想要的数组

    【讨论】:

      【解决方案3】:

      Ansible 2.2 开始,include_vars (link) 模块已经扩展了不少。

      现在可以执行以下操作:

      - include_vars:
          name: 'apps'
          dir: '../vars'
          extensions:
            - 'yaml'
            - 'yml'
      

      name 是那里的关键。从模块页面:

      分配包含变量的变量的名称。如果省略(null),它们将成为顶级变量。

      这允许您转换:

      vars/
          app1.yml
          app2.yml
          ...
      

      app1.yml:

      name: app1
      git_repo: https://github.com/philgyford/app1.git
      # ...
      

      app2.yml:

      name: app2
      git_repo: https://github.com/philgyford/app2.git
      # ...
      

      进入...

      apps:
        - name: app1
          git_repo: https://github.com/philgyford/app1.git
          # ...
        - name: app2
          git_repo: https://github.com/philgyford/app2.git
          # ...
      

      【讨论】:

      • 您是否尝试过您发布的解决方案?因为 Ansible 不接受 vars 文件作为列表,所以它必须是字典。它将throw an error ../vars/app1.yml must be stored as a dictionary/hash.
      • 如果要合并两个子数组怎么办?比如合并了两个apps vars?
      • @techraf - 你是对的。连字符需要从 appX.yml 文件中删除,以便它们成为字典。无论如何,它们都是不必要的,因为该列表正是该方法要分解为单独文件的内容。我已经更新了我的答案以提供一个有效的示例。对此感到抱歉。
      【解决方案4】:

      使用属性name 并将包含的变量放入字典中。使字典的名称符合您的需要。例如

          - name: Load var files
            include_vars:
              file: "{{ item }}"
              name: "incl_vars_{{ item|basename|splitext|first }}"
            with_fileglob:
              - vars/*.yml
      

      然后,使用查找插件varnames(2.8 版中的新功能),查找所有字典,并迭代列表。在循环中使用查找插件vars(2.5 版中的新功能)并创建列表apps。例如

          - set_fact:
              apps: "{{ apps|default([]) + [lookup('vars', item)] }}"
            loop: "{{ query('varnames', '^incl_vars_(.*)$') }}"
      

      给予

      apps:
        - git_repo: https://github.com/philgyford/app2.git
          name: app2
        - git_repo: https://github.com/philgyford/app1.git
          name: app1
      

      如果您愿意,可以将列表转换为字典

          - set_fact:
              apps: "{{ dict(_keys|zip(apps)) }}"
            vars:
              _keys: "{{ apps|map(attribute='name')|list }}"
      

      给予

      apps:
        app1:
          git_repo: https://github.com/philgyford/app1.git
          name: app1
        app2:
          git_repo: https://github.com/philgyford/app2.git
          name: app2
      

      【讨论】:

      • 谢谢弗拉基米尔。我使用了您的解决方案的轻微变化,但我使用了您的想法和框架来完成繁重的工作......我在文件中的数据结构有点不同。再次感谢您。
      【解决方案5】:

      从 Ansible v2.0 开始,您可以做到:

      - name: merging hash_a and hash_b into hash_c
        set_fact: hash_c="{{ hash_a|combine(hash_b) }}"
      

      在 Ansible 过滤器下查看更多信息 - 组合哈希/字典(来自 Jinja2)

      【讨论】:

      • 你能展示如何对 vars 文件执行此操作吗?我必须对 vars 文件中的每个数组都执行此操作吗?我可以以某种方式对整个 vars 文件执行此操作吗?
      【解决方案6】:

      想要通过获取与模式匹配的变量列表来发布可能的替代解决方案,然后您可以处理所有这些变量,类似于手动合并。

      以下代码块给出了一个拉取与特定模式匹配的所有变量并循环的示例。您可以将它们合并设置一个新事实,或者简单地单独处理它们。

      - name: "debug2"   debug:
          msg: "value is: {{ lookup('vars', item) }} "   
        loop: "{{ hostvars[inventory_hostname] | select('match', '^linux_hosts_entries') |list  }}"
      

      更多详情请参阅以下post

      【讨论】:

      • 请在回答中提供帖子的核心细节,谢谢。
      【解决方案7】:

      如果您不想更改哈希的默认行为。我建议你我的解决方案。 当需要合并它们时,我定义了多个变量(具有相同的正则表达式),我从主机变量中过滤它们。例如:

      - name: Combine variables with prefix enabled_services
        debug:
          msg: "{{ hostvars[inventory_hostname].keys() | map('regex_search', 'enabled_services.*') | select('string') | list }}"
      

      【讨论】:

        【解决方案8】:

        我用过这个:

        - name: Load data
          run_once: true
          set_fact:
            my_data: "{{ my_data|default([]) + [lookup('file', item) | from_yaml] }}"
          with_fileglob: "../data/*.yml" # relative to this file
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-22
          • 2016-07-20
          • 2022-01-24
          • 2020-01-25
          相关资源
          最近更新 更多