python - 编辑现有的 yaml 文件但保留原始注释

标签 python yaml pyyaml ruamel.yaml

我正在尝试创建一个 Python 脚本,将我们的 IPtables 配置转换为 YAML 文件中的防火墙 multi。我最初使用的是 pyyaml,但是后来发现这会删除我需要保留的所有注释,我发现 ruamel.yaml 可以用于保留注释,但是,我正在努力让它发挥作用。

import sys
import io
import string
from collections import defaultdict
import ruamel.yaml 


#loading the yaml file 

try:
      config = ruamel.yaml.round_trip_load(open('test.yaml'))
except ruamel.yaml.YAMLError as exc:
      print(exc)

print (config)

# Output class
#this = defaultdict(list)
this = {}
rule_number = 200
iptables_key_name = "ha247::firewall_addrule::firewall_multi"


# Do stuff here
for key, value in config.items():
 # Maipulate iptables rules only
   if key == 'iptables::rules':

# Set dic withim iptables_key_name
     this[iptables_key_name] = {}
     for rule, rule_value in value.items():

# prefix rule with ID
         new_rule =("%s %s" % (rule_number,rule))
         rule_number = rule_number + 1



# Set dic within [iptables_key_name][rule]
         this[iptables_key_name][new_rule] = {}
# Ensure we have action
         this[iptables_key_name][new_rule]['action'] = 'accept'
         for b_key, b_value in rule_value.items():
# Change target to action as rule identifier
             b_key = b_key.replace('target','action')
# Save each rule and ensure we are lowrcase
             this[iptables_key_name][new_rule][b_key] = str(b_value).lower()

  elif key == 'ha247::security::enable': 
      this['ha247::security_firewall::enable'] = value

  elif key == 'iptables::safe_ssh':
      this['ha247::security_firewall::safe_ssh'] = value

  else:
# Print to yaml
     this[key] = value


# Write YAML file
  with io.open('result.yaml', 'w', encoding='utf8') as outfile:
       ruamel.yaml.round_trip_dump(this, outfile, default_flow_style=False, allow_unicode=True)

输入文件(test.yaml)

---

# Enable default set of security rules


# Configure firewall
iptables::rules:
 ACCEPT_HTTP:
    port: '80'
 HTTPS:
    port: '443'

# Configure the website
simple_nginx::vhosts:
    <doamin>:
     backend: php-fpm
     template: php-magento-template
     server_name: 
     server_alias: www.
     document_root: /var/www/
     ssl_enabled: true
     ssl_managed_enabled: true
     ssl_managed_name: www.
     force_www: true

result.yaml 的输出

ha247::firewall_addrule::firewall_multi:
  200 ACCEPT_HTTP:
    action: accept
    port: '80'
  201 HTTPS:
    action: accept
    port: '443'

ha247::security_firewall::enable: true
ha247::security_firewall::safe_ssh: false
simple_nginx::ssl_ciphers:     
simple_nginx::vhosts:
 <domain>:
    backend: php-fpm
    document_root: /var/www/
    force_www: true
    server_alias: www.
    server_name: .com
    ssl_enabled: true
    ssl_managed_enabled: true
    ssl_managed_name: www.
    template: php-magento-template

这就是问题所在,正如您所看到的,它更改了我们需要保留的所有格式并删除了注释,另一个问题是它删除了顶部的三个连字符,这将使配置管理器无法读取文件。

最佳答案

您无法完全获得您想要的结果,因为您的映射缩进不一致,因为映射的缩进为 1、2、3 和 4 个位置。据记录,ruamel.yaml只有一项设置应用于所有映射(默认为 2)。

目前,不会在输入时分析文档开始(和结束)标记,因此您只需做一些最少的额外工作。

然而,最大的问题是您对使用往返装载机和自卸车的含义的误解。它的目的是将 YAML 文档加载到 Python 数据结构中,更改该数据结构,然后写出相同的数据结构。您突然创建一个新的数据结构 ( this ),从 YAML 加载的数据结构 ( config ) 中分配一些值,然后写出该新数据结构 ( this )。从您调用print() ,您看到您正在加载 CommentedMap作为根数据结构,以及普通的 Python dict当然不知道您可能已加载并附加到 config 的任何评论.

因此,首先看看使用一个最小的程序会得到什么,该程序可以加载和转储输入文件而不(显式)更改任何内容。我将使用新的 API,并建议您也这样做,尽管您也可以使用旧的 API 来完成此操作。在新的API中 allow_unicode默认 True .

import sys
from ruamel.yaml import YAML

yaml = YAML()
yaml.explicit_start = True
yaml.indent(mapping=3)
yaml.preserve_quotes = True  # not necessary for your current input

with open('test.yaml') as fp:
    data = yaml.load(fp)
yaml.dump(data, sys.stdout)

这给出:

---

# Enable default set of security rules


# Configure firewall
iptables::rules:
   ACCEPT_HTTP:
      port: '80'
   HTTPS:
      port: '443'

# Configure the website
simple_nginx::vhosts:
   <doamin>:
      backend: php-fpm
      template: php-magento-template
      server_name:
      server_alias: www.
      document_root: /var/www/
      ssl_enabled: true
      ssl_managed_enabled: true
      ssl_managed_name: www.
      force_www: true

这仅与您的输入不同 test.yaml具有一致的缩进(即 diff -b 没有差异)。

<小时/>

您的代码实际上不起作用(由于缩进而出现语法错误),如果起作用,则不清楚

ha247::security_firewall::enable: true
ha247::security_firewall::safe_ssh: false
simple_nginx::ssl_ciphers:   

在输出中是怎么来的,也不怎么<doamin>更改于 <domain> (你确实在那里做了一些可疑的事情,否则 <domain> 值中的键将不会神奇地被排序。

假设输入 test.yaml :

---

# Enable default set of security rules


# Configure firewall
iptables::rules:
 ACCEPT_HTTP:
    port: '80'
 HTTPS:
    port: '443'

ha247::security::enable: true         # EOL Comment
iptables::safe_ssh: false
simple_nginx::ssl_ciphers:
# Configure the website
simple_nginx::vhosts:
    <doamin>:
     backend: php-fpm
     template: php-magento-template
     server_name:
     server_alias: www.
     document_root: /var/www/
     ssl_enabled: true
     ssl_managed_enabled: true
     ssl_managed_name: www.
     force_www: true

以及以下程序:

import sys
from ruamel.yaml import YAML

yaml = YAML()
yaml.explicit_start = True
yaml.indent(mapping=3)
yaml.preserve_quotes = True  # not necessary for your current input

with open('test.yaml') as fp:
    data = yaml.load(fp)


key_map = {
    'iptables::rules': ['ha247::firewall_addrule::firewall_multi', None, 200],
    'ha247::security::enable': ['ha247::security_firewall::enable', None],
    'iptables::safe_ssh': ['ha247::security_firewall::safe_ssh', None],
}

for idx, key in enumerate(data):
    if key in key_map:
        key_map[key][1] = idx

rule_number = 200

for key in key_map:
    km_val = key_map[key]
    if km_val[1] is None:  # this is the index in data, if found
        continue
    # pop the value and reinsert it in the right place with the new name
    value = data.pop(key)
    data.insert(km_val[1], km_val[0], value)
    # and move the key related comments
    data.ca._items[km_val[0]] = data.ca._items.pop(key, None)
    if key == 'iptables::rules':
        data[km_val[0]] = xd = {}  # normal dict nor comments preserved
        for rule, rule_value in value.items():
            new_rule = "{} {}".format(rule_number, rule)
            rule_number += 1
            xd[new_rule] = nr = {}
            nr['action'] = 'accept'
            for b_key, b_value in rule_value.items():
                b_key = b_key.replace('target', 'action')
                nr[b_key] = b_value.lower() if isinstance(b_value, str) else b_value


yaml.dump(data, sys.stdout)

你得到:

---

# Enable default set of security rules


# Configure firewall
ha247::firewall_addrule::firewall_multi:
   200 ACCEPT_HTTP:
      action: accept
      port: '80'
   201 HTTPS:
      action: accept
      port: '443'

ha247::security_firewall::enable: true # EOL Comment
ha247::security_firewall::safe_ssh: false
simple_nginx::ssl_ciphers:
# Configure the website
simple_nginx::vhosts:
   <doamin>:
      backend: php-fpm
      template: php-magento-template
      server_name:
      server_alias: www.
      document_root: /var/www/
      ssl_enabled: true
      ssl_managed_enabled: true
      ssl_managed_name: www.
      force_www: true

这应该是一个良好的起点。

请注意,我使用了 .format()而不是老式的%格式化。我也只小写b_value如果它是一个字符串,您的代码将例如将整数转换为字符串,这会导致输出中出现引号,而其中没有引号。

关于python - 编辑现有的 yaml 文件但保留原始注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46786876/

相关文章:

apache-kafka - CloudFormation - 如何将 bootsrap 参数添加到 Ksql Server

ruby - 如何将 yml 文件的内容传递到我的 erb 模板?

python - 使用 PyYAML 与 Python 的 YAML

python - 使用 PyYaml 解析可能无效的 YAML

python - 如何使用 Pandas 从 DataFrame 或 np.array 中的列条目创建字典

python - 在 python 2 中为类设置 __doc__

python - 如何获得第 3 方 cookie?

javascript - 自动重定向到 div,无需用户干预

string - 有没有办法表示在 YAML 文档中的多行上没有任何空格的长字符串?

python - PyYAML 可以解析 iso8601 日期吗?