代码之家  ›  专栏  ›  技术社区  ›  Rustylee

将JSON文件读取到Ansible中,并替换或向导入的数据结构添加值

  •  0
  • Rustylee  · 技术社区  · 3 年前

    我们在每个设备的单独JSON文件中对38个网络设备进行了配置。

    我们使用Ansible剧本来查找相关主机,并使用F5部署模块交付其配置

      vars:
        filename: "{{ansible_host}}.json"
        ffullpath: "/runner/project/do/net-prod/configs/{{filename}}"
    
      tasks:
      - name: Deploy the DO Code to the F5
        bigip_do_deploy:
          content: "{{ lookup('file', '{{ffullpath}}') }}"
          dry_run: "{{dryrun}}"
        register: result
    

    JSON配置文件在所有配置文件中都有通用元素。例如,四个DNS服务器的列表。与其在所有配置文件中重复这一点,不如将它们放在一个单独的公共文件中,我也可以在配置文件中导入或替换它。JSON配置文件中还有其他敏感数据,如专用RSI密钥。我还想用Ansible中的一个变量来代替这些。我很乐意在剧本中使用变量,但不知道必须在JSON配置文件中实现这一点。

    我了解到你不能在Ansible中更改变量,并在网上搜索了一段时间的解决方案。其他人也做过类似的事情吗?

    我尝试了各种方法来操纵ansible中的变量。甚至将文件加载到DICT并试图更改它也没有成功。

    0 回复  |  直到 3 年前
        1
  •  0
  •   Vladimir Botka    3 年前

    问: “读取JSON文件并将值替换或添加到数据结构中。”


    上下文:模块的F5文档 bigip_do_deploy 建议如何动态创建内容。然而 样板 插件 documentation 说“模板查找插件……不是一种鼓励的做法……应该只用于绝对最小的配置”,原因很荒谬,“以防止你的行动手册变得太大”:

    所容纳之物 :要在系统上配置的声明。 此参数最常与 文件或模板查找插件 。有关正确用法,请参阅示例部分。 对于任何高级或带有格式的内容,请考虑使用模板查找。这还可以用于直接在YAML中指定应用程序服务配置,但是 不是鼓励的做法 并且,如果使用的话,应该只用于绝对最小的配置 防止你的花花公子变得太大 。如果您的内容包含加密值(如密文、密码短语等),则返回的更改值将始终为true。如果您使用to_nice_json过滤器,它将导致该模块失败,因为该过滤器的目的是将json格式化为可读的,并且该过程包括插入“破坏json验证器的额外字符”。


    A: 你的尝试毫无意义。你应该使用 templating 即使 文档 是说这不是一种鼓励的做法。 Templating 是Ansible的核心功能,应在需要时广泛使用。

    在这个解决方案中,想法是在YAML中创建内容,并将其转换为JSON,作为最后一步。从技术上讲,这意味着转换您的文件 {{ ansible_host }}.json 到模板 {{ ansible_host }}.yaml.j2 和使用 template 查找插件

      tasks:
        - name: Deploy the DO Code to the F5
          bigip_do_deploy:
            content: '{{ lookup("template", template)|from_yaml|to_json }}'
            dry_run: "{{ dryrun }}"
          register: result
          vars:
            template: "{{ ansible_host }}.yaml.j2"
    

    注意事项:

    • 参数的类型 content 是 raw ,即模块 bigip_dodeploy 不检查 所容纳之物 是否为有效的JSON。

    • 没有 open issues with filter to_json ,即该过滤器应按预期工作。

    • 查看功能请求 #75962 关于嵌套模板 {{ lookup('template', x) | from_yaml | to_json }} 。这个解决方案也建议使用这种管道。

    • 查看请求 #73472 为了改进文档 backlog 。


    在第一个示例中,对所有组件分别进行了解释和测试。在第二个示例中,使用相同的组件来动态创建内容,并将敏感数据的范围限制为单个任务。最后一个示例显示了如何将多个模板与多个主机和网络设备一起使用。这些示例没有提供网络设备的有效配置。目标是展示如何使用具有多个变量源的模板(group_var、host_var、play-var、task-var、vault文件等)。

    示例1。创建YAML配置文件

    有很多选择 setup 可靠。例如,让我们创建下面的简单项目进行测试

    shell> tree .
    .
    ├── ansible.cfg
    ├── files
    ├── group_vars
    │   └── all
    │       ├── net_dev_common.yml
    │       └── net_dev_vault.yml
    ├── hosts
    ├── pb.yml
    └── templates
        └── net_dev.yaml.j2
    
    shell> cat ansible.cfg 
    [defaults]
    gathering = explicit
    collections_path = $HOME/.local/lib/python3.9/site-packages/
    inventory = $PWD/hosts
    roles_path = $PWD/roles
    retry_files_enabled = false
    stdout_callback = yaml
    

    在中创建通用变量 group_vars/all

    shell> cat group_vars/all/net_dev_common.yml 
    nd_dns_common: [8.8.8.8, 8.8.4.4]
    

    在中创建通用vault变量 group_vars/all

    shell> cat group_vars/all/net_dev_vault.yml 
    nd_passwd:
      nd1: passwd_for_nd1
      nd2: passwd_for_nd2
      nd3: passwd_for_nd3
    

    Encrypt 文件

    shell> ansible-vault encrypt group_vars/all/net_dev_vault.yml 
    Encryption successful
    
    shell> cat group_vars/all/net_dev_vault.yml 
    $ANSIBLE_VAULT;1.1;AES256
    64373537613238313530376438313030643339383336313436363738343637646362626430396236
    6135646536323563336539663364313966633066353231610a353365306338633338646261326661
    31613062393934303331626238623836383862616338636564393630613865356333363239373731
    6639383961356635640a663565336238613232346331663363616438666532646532663137333930
    37323537306264633161326433646131353162323136343434316166616432313465626632643161
    65616136386166336331306134613163353630306562333765373839623831356562313365306563
    62333464303264646139323065663739633262663131666231646638643134666433653961366665
    35626230316533323761
    

    创建简单库存

    shell> cat hosts
    localhost
    

    创建一个简单的测试模板。为了更好的可读性,该模板将在YAML中创建配置文件。所有三个参数都将动态设置

    shell> cat templates/net_dev.yaml.j2 
    {% filter from_yaml %}
    device: {{ item }}
    dns: {{ nd_dns_common }}
    passwd: {{ nd_passwd[item] }}
    {% endfilter %}
    

    例如,下面播放

    shell> cat pb.yml 
    - hosts: localhost
    
      vars:
    
        net_devs: [nd1, nd2, nd3]
    
      tasks:
    
        - name: Create configuration files
          copy:
            dest: "{{ playbook_dir }}/files/{{ item }}.yaml"
            content: "{{ lookup('template', 'net_dev.yaml.j2')|to_nice_yaml }}"
          loop: "{{ net_devs }}"
          when: nd_init|d(false)|bool
    

    给予

    shell> ansible-playbook pb.yml -e nd_init=true
    
    PLAY [localhost] ******************************************************************************
    
    TASK [Create configuration files] *************************************************************
    changed: [localhost] => (item=nd1)
    changed: [localhost] => (item=nd2)
    changed: [localhost] => (item=nd3)
    

    并创建配置文件

    shell> tree files/
    files/
    ├── nd1.yaml
    ├── nd2.yaml
    └── nd3.yaml
    
    0 directories, 3 files
    
    shell> cat files/nd1.yaml 
    device: nd1
    dns:
    - 8.8.8.8
    - 8.8.4.4
    passwd: passwd_for_nd1
    
    shell> cat files/nd2.yaml 
    device: nd2
    dns:
    - 8.8.8.8
    - 8.8.4.4
    passwd: passwd_for_nd2
    
    shell> cat files/nd3.yaml 
    device: nd3
    dns:
    - 8.8.8.8
    - 8.8.4.4
    passwd: passwd_for_nd3
    

    读取配置文件并将内容转换为JSON

        - name: Test configuration files
          command: echo '{{ content|to_json }}'
          loop: "{{ net_devs }}"
          register: out
          vars:
            file: "files/{{ item }}.yaml"
            content: "{{ lookup('file', file)|from_yaml }}"
          when: nd_deploy|d(false)|bool
        - debug:
            msg: "{{ out.results|map(attribute='stdout') }}"
          when: nd_deploy|d(false)|bool
    

    以下调试任务显示筛选器 to_json 将文件的内容转换为 valid JSON

    shell> ansible-playbook pb.yml -e nd_deploy=true
    
    PLAY [localhost] ******************************************************************************
    
    TASK [Create configuration files] *************************************************************
    skipping: [localhost] => (item=nd1) 
    skipping: [localhost] => (item=nd2) 
    skipping: [localhost] => (item=nd3) 
    skipping: [localhost]
    
    TASK [Test configuration files] ***************************************************************
    changed: [localhost] => (item=nd1)
    changed: [localhost] => (item=nd2)
    changed: [localhost] => (item=nd3)
    
    TASK [debug] **********************************************************************************
    ok: [localhost] => 
      msg:
      - '{"device": "nd1", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd1"}'
      - '{"device": "nd2", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd2"}'
      - '{"device": "nd3", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd3"}'
    
    PLAY RECAP ************************************************************************************
    localhost: ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
    

    完整的测试剧本示例

    shell> cat pb.yml 
    - hosts: localhost
    
      vars:
    
        net_devs: [nd1, nd2, nd3]
    
      tasks:
    
        - name: Create configuration files
          copy:
            dest: "{{ playbook_dir }}/files/{{ item }}.yaml"
            content: "{{ lookup('template', 'net_dev.yaml.j2')|to_nice_yaml }}"
          loop: "{{ net_devs }}"
          when: nd_init|d(false)|bool
    
        - name: Test configuration files
          command: echo '{{ content|to_json }}'
          loop: "{{ net_devs }}"
          register: out
          vars:
            file: "files/{{ item }}.yaml"
            content: "{{ lookup('file', file)|from_yaml }}"
          when: nd_deploy|d(false)|bool
        - debug:
            msg: "{{ out.results|map(attribute='stdout') }}"
          when: nd_deploy|d(false)|bool
    

    示例2。动态创建内容。将密码的范围限制为单个任务。

    在第一个示例中,存储在字典中的密码 nd_passwd ,在整部剧的范围内都有。解密后的密码也存储在文件中 files/nd*.yml .在这个项目中,我们限制了变量的范围 nd_passwd 到单个任务并动态创建内容

    shell> tree .
    .
    ├── ansible.cfg
    ├── files
    │   └── net_dev_vault.yml
    ├── group_vars
    │   └── all
    │       └── net_dev_common.yml
    ├── hosts
    ├── pb.yml
    └── templates
        └── net_dev.yaml.j2
    

    我们移动了文件 net_dev_svault.yml 从…起 group_vars/all 进入目录 文件 并更新了剧本。其他文件与第一个示例中的相同。

    shell> cat pb.yml 
    - hosts: localhost
    
      vars:
    
        net_devs: [nd1, nd2, nd3]
    
      tasks:
    
        - name: Test configuration files
          command: echo '{{ content|to_json }}'
          loop: "{{ net_devs }}"
          register: out
          vars:
            content: "{{ lookup('template', 'net_dev.yaml.j2')|from_yaml }}"
            vault: "{{ lookup('file', 'net_dev_vault.yml')|from_yaml }}"
            nd_passwd: "{{ vault.nd_passwd }}"
            
        - debug:
            msg: "{{ out.results|map(attribute='stdout') }}"
    

    下面的调试任务显示了查找插件 样板 与过滤器一起 to_json 动态创建了相同的有效JSON内容

    shell> ansible-playbook pb.yml
    
    PLAY [localhost] ******************************************************************************
    
    TASK [Test configuration files] ***************************************************************
    changed: [localhost] => (item=nd1)
    changed: [localhost] => (item=nd2)
    changed: [localhost] => (item=nd3)
    
    TASK [debug] **********************************************************************************
    ok: [localhost] => 
      msg:
      - '{"device": "nd1", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd1"}'
      - '{"device": "nd2", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd2"}'
      - '{"device": "nd3", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd3"}'
    
    PLAY RECAP ************************************************************************************
    localhost: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    示例3。多个模板、多个主机和多个网络设备

    在以下项目中

    shell> tree .
    .
    ├── ansible.cfg
    ├── files
    │   └── net_dev_vault.yml
    ├── group_vars
    │   └── all
    │       └── net_dev_common.yml
    ├── hosts
    ├── host_vars
    │   ├── host1
    │   │   └── nd_local.yml
    │   └── host2
    │       └── nd_local.yml
    ├── pb.yml
    └── templates
        ├── net_dev_default.yaml.j2
        └── net_dev_extended.yaml.j2
    

    创建两个模板

    shell> cat templates/net_dev_default.yaml.j2 
    {% filter from_yaml %}
    device: {{ item }}
    dns: {{ nd_dns_common }}
    passwd: {{ nd_passwd[item] }}
    {% endfilter %}
    
    shell> cat templates/net_dev_extended.yaml.j2 
    {% filter from_yaml %}
    device: {{ item }}
    dns: {{ nd_dns_common }}
    passwd: {{ nd_passwd[item] }}
    is_clustered: {{ nd_clustered[item]|d(nd_clustered.default) }}  
    is_license_expired: {{ nd_expired[item]|d(true) }}  
    is_virtual: {{ nd_virtual[item]|d(false) }}  
    {% endfilter %}
    

    将两台主机放入库存

    shell> cat hosts
    [test]
    host1 ansible_host=test_11
    host2 ansible_host=test_13
    

    并创建 主机虚拟机(_V)

    shell> cat host_vars/host1/nd_local.yml 
    net_devs: [nd1, nd2, nd3]
    net_devs_templates:
      net_dev_default: [nd1]
      net_dev_extended: [nd2, nd3]
    nd_clustered:
      default: false
      nd3: true
    nd_expired:
      default: true
      nd3: false
    nd_virtual:
      default: false
      nd3: true
    
    shell> cat host_vars/host2/nd_local.yml 
    net_devs: [nd1, nd2]
    net_devs_templates:
      net_dev_default: [nd1]
      net_dev_extended: [nd2]
    nd_clustered:
      default: false
      nd2: true
    nd_expired:
      default: true
      nd2: false
    nd_virtual:
      default: false
      nd2: true
    

    剧本

    shell> cat pb.yml 
    - hosts: all
    
      tasks:
    
        - name: Test configuration files
          command: echo '{{ lookup("template", template)|from_yaml|to_json }}'
          loop: "{{ net_devs }}"
          register: out
          vars:
            template: "{{ net_devs_templates|dict2items|
                          selectattr('value', 'contains', item)|
                          map(attribute='key')|first }}.yaml.j2"
            vault: "{{ lookup('file', 'net_dev_vault.yml')|from_yaml }}"
            nd_passwd: "{{ vault.nd_passwd }}"
            
        - debug:
            msg: "{{ out.results|map(attribute='stdout') }}"
    

    给予

    shell> ansible-playbook pb.yml 
    
    PLAY [all] **************************************************************************************************************************************************
    
    TASK [Test configuration files] *****************************************************************************************************************************
    changed: [host1] => (item=nd1)
    changed: [host2] => (item=nd1)
    changed: [host2] => (item=nd2)
    changed: [host1] => (item=nd2)
    changed: [host1] => (item=nd3)
    
    TASK [debug] ************************************************************************************************************************************************
    ok: [host1] => 
      msg:
      - '{"device": "nd1", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd1"}'
      - '{"device": "nd2", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd2", "is_clustered": false, "is_license_expired": true, "is_virtual": false}'
      - '{"device": "nd3", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd3", "is_clustered": true, "is_license_expired": false, "is_virtual": true}'
    ok: [host2] => 
      msg:
      - '{"device": "nd1", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd1"}'
      - '{"device": "nd2", "dns": ["8.8.8.8", "8.8.4.4"], "passwd": "passwd_for_nd2", "is_clustered": true, "is_license_expired": false, "is_virtual": true}'
    
    PLAY RECAP **************************************************************************************************************************************************
    host1: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    host2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0