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

Edit existing yaml file but keeping original comments

我正在尝试创建一个 Python 脚本,它将我们的 IPtables 配置转换为 YAML 文件中的防火墙多。我最初使用的是 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 如果它是一个字符串,你的代码将例如将整数转换为字符串,这将导致输出中出现引号,其中 none 开头。