Python PyYML在ssh公钥中插入换行符

Python PyYML在ssh公钥中插入换行符,python,yaml,Python,Yaml,我正在做一个项目,从MySQL数据库中提取用户信息,并将其格式化为yaml文件,Ansible可以读取该文件并将其用作vars文件。我需要所有正常的用户信息、用户名、电子邮件等,以及数据库中的公共ssh密钥 问题是,PyYAML在pubkey的电子邮件部分之前插入了一个额外的换行符,我不知道为什么。下面是一个简单的例子: import yaml yamldict = { "users": [] } yamldict["users"].append({ "username": "use

我正在做一个项目,从MySQL数据库中提取用户信息,并将其格式化为yaml文件,Ansible可以读取该文件并将其用作vars文件。我需要所有正常的用户信息、用户名、电子邮件等,以及数据库中的公共ssh密钥

问题是,PyYAML在pubkey的电子邮件部分之前插入了一个额外的换行符,我不知道为什么。下面是一个简单的例子:

import yaml

yamldict = { "users": [] }

yamldict["users"].append({
    "username": "user",
    "name": "user",
    "sshkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkILf8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQYrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqhP6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMevxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com"
})
哪些产出:

users:
- name: user
  sshkey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkILf8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQYrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqhP6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMevxPOijjz4EiI1Ad4U6dDJrFlT0asYH
    user@email.com
  username: user
我尝试了许多不同的方法去除多余的空格、换行符和回车符。我还尝试将这个dict转换为json,ssh密钥看起来很好,然后在json上运行yaml.dump,它仍然给了我额外的换行符


知道我做错了什么吗?

YAML可以用多种方式将字符串表示为标量:普通(不带引号)、单引号、双引号、文字样式或折叠样式。键
sshkey
的值是纯标量

YAML也希望具有可读性,而长行的可读性不是很强。因此,有一些规则如何包装宽标量强制的长线。将包装作为
sshkey
值的普通标量。这意味着YAML文档中有一个换行符,但它所表示的标量字符串中没有换行符,在读取YAML文档时,该换行符被“展开”

通过使用
yamldict
定义运行以下命令可以看到这一点:

with open('tmp.yaml', 'w') as fp:
    yaml.safe_dump(yamldict, fp)
with open('tmp.yaml') as fp:
    data = yaml.safe_load(fp)

assert '\n' in data['users'][0]['sshkey']
这将抛出一个错误,因为重新加载的ssh密钥中没有换行符

因此,您的程序是好的,但您一直在做错误的事情是您没有阅读YAML规范,特别是上的部分


现在,由于ssh密钥中没有足够的空间,这种特殊的折叠并不能真正提高可读性。因此,您不妨增加行宽度,将所有内容放在一行上。您可以使用PyYAML实现这一点,但我建议您使用PyYAML,它支持较新的YAML 1.2标准,允许映射和序列使用单独的缩进值,并且修复了许多PyYAML问题(免责声明:我是该软件包的作者):

此转储为:

users:
- username: user
  name: user
  sshkey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkILf8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQYrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqhP6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMevxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com
您可以做的另一件事是将该键作为文字样式标量转储。为此,您需要包含一个import:
from ruamel.yaml.scalarstring import PreservedScalarString
,然后在从MySQL读取数据后,在某处将键定义为preserved scalarstring。例如,在您的示例中,您可以:

for m in yamldict['users']:
    m['sshkey'] = PreservedScalarString(m['sshkey'])
假设删除
yaml.width=1024
,并包含
yaml.indent(sequence=4,offset=2)
,则将转储为:

users:
  - username: user
    name: user
    sshkey: |-
      ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkILf8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQYrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqhP6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMevxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com
其中
|-
表示文字样式块标量



如果您需要坚持使用PyYAML,那么请使用
safe_dump(yamldict,…,width=1024)
,但是没有简单的方法可以将键转储为文字样式块标量,也不能仅缩进序列)。

这是我的解决方案,使用PyYAML:

import yaml

def add_line_breaks(long_string, line_len=70):
    return '\n'.join(long_string[i:i+line_len] for i in range(0, len(long_string), line_len))

def long_str_representer(dumper, data): # https://stackoverflow.com/a/33300001/10590519
    if len(data.splitlines()) > 1:  # check for multiline string
        return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
    return dumper.represent_scalar('tag:yaml.org,2002:str', data)

yaml.add_representer(str, long_str_representer)

yamldict = { "users": [] }

yamldict["users"].append({
    "username": "user",
    "name": "user",
    "sshkey": add_line_breaks("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkILf8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQYrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqhP6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMevxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com")
})

print(yaml.dump(yamldict, default_flow_style=False))
这将输出:

users:
- name: user
  sshkey: |-
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkIL
    f8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQ
    Yrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB
    3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqh
    P6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMe
    vxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com
  username: user

这真的重要吗?通常,键后面的部分是纯描述性的,对算法本身没有意义。我不确定,是吗?这将最终用于创建ssh帐户,我不确定额外的换行是否会导致ansible的授权密钥模块出现问题……我想我应该测试一下,也许不再痴迷于此……感谢全面的回答,这太棒了。不幸的是,我现在需要继续使用PyYAML,因为我运行的是旧的Centos6服务器,不想到处安装新东西。但我肯定会在我有更多控制权的其他地方开始使用你的库。@Jasonewitt通常情况下,库的选择受到环境的限制,这就是为什么我在PyYAML中包括了如何做这些。我总是使用VirtualNVS(与系统Python或更新版本一起)作为我制作的实用程序/程序,以避免弄乱系统Python的安装。顺便说一句,有一个-1分的公认答案很有趣,我最近一定踩到了别人的脚。。。
users:
- name: user
  sshkey: |-
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHV/xbvOHuPq6WbBhtmjUWKYPrqQlkIL
    f8b/I6V9dZVBPzmhRZFCAf/gWny0hmZ95bVRED4iCSTCtN3Lq2VZiZ/kwBO7Y9E4vr1wVQ
    Yrr4IIwEhdaifZmWFLlwOXbt76dxJQs2xS9Z5ZQjEzZBFZqgYu42QbSi7tKBNSaLadOWbB
    3sq0IOzCZeSgrELlZIuUy7u1RbcS4w2Y29S3XLrbi2yVdVbPW8B9PfsG1n4q2/XR7w3gqh
    P6c8ibO4jYpADLZuHZvuoVpjKINO4kSdrwUfD8rl3MBIAD/Nu9sy0bIiKdSONQohxcsjMe
    vxPOijjz4EiI1Ad4U6dDJrFlT0asYH user@email.com
  username: user