编辑现有的 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 开头。
我正在尝试创建一个 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 开头。