Гибкие добавления новых данных в файлы yaml

У меня разные файлы yaml, которые могут иметь различную вложенную структуру

file1.yaml:

test3:
  service1: 
      name1: |
        "somedata"
      name2: |
          "somedata"

file2.yaml:

test1: 
  app1: 
     app2:|
       "somedata"
  app7:
     key2: | 
       "testapp"

Таким образом, вы можете видеть, что структура файлов yaml может отличаться.

Вопрос в том, могу ли я как-то гибко управлять добавлением некоторых данных в определенные блоки этих файлов?

Например, в файле1 я хочу написать ключ vaue на уровне name1 и name 2 keys или service1:

test3:
  service1: 
      name1: |
        "somedata"
      name2: |
          "somedata"
      my-appended-key:| 
              "my appended value"
  my_second_appended_key: | 
          "my second appended valye"

и так далее.

Поэтому идея состоит в том, чтобы иметь возможность указать, под каким вложенным блоком в yaml я хочу добавить данные.

У меня разные файлы yaml, которые могут иметь различную вложенную структуру

file1.yaml:

test3:
  service1: 
      name1: |
        "somedata"
      name2: |
          "somedata"

file2.yaml:

test1: 
  app1: 
     app2:|
       "somedata"
  app7:
     key2: | 
       "testapp"

Таким образом, вы можете видеть, что структура файлов yaml может отличаться.

Вопрос в том, могу ли я как-то гибко управлять добавлением некоторых данных в определенные блоки этих файлов?

Например, в файле1 я хочу написать ключ vaue на уровне name1 и name 2 keys или service1:

test3:
  service1: 
      name1: |
        "somedata"
      name2: |
          "somedata"
      my-appended-key:| 
              "my appended value"
  my_second_appended_key: | 
          "my second appended valye"

и так далее.

На данный момент я делаю это для конкретного случая относительно структуры файла yaml. Вот часть моего кода:

import gnupg
import re
import argparse

def NewPillarFile():
    with open(args.sensitive) as sensitive_data:
        with open(args.encrypted, "w") as encrypted_result:
            encrypted_result.write('#!yaml|gpg\n\nsecrets:\n    '+args.service+':\n')
            for line in sensitive_data:
                encrypted_value = gpg.encrypt(re.sub(r'^( +?|[A-Za-z0-9]|[A-Za]|[0-9])+( +)?'+args.separator+'( +)?','',line,1), recipients=args.resident, always_trust=True)
                if not encrypted_value.ok:
                    print(encrypted_value.status, '\n', encrypted_value.stderr)
                    break
                line = re.sub(r'^( +)?','',line)
                encrypted_result.write('        '+re.sub(r'( +)?'+args.separator+'.*',': |',line))
                encrypted_result.write(re.sub(re.compile(r'^', re.MULTILINE), '            ', encrypted_value.data.decode())+'\n')

def ExistingPillarFile():
    with open(args.sensitive) as sensitive_data:
        with open(args.encrypted, "a") as encrypted_result:
            encrypted_result.write('    '+args.service+':\n')
            for line in sensitive_data:
                encrypted_value = gpg.encrypt(
                    re.sub(r'^( +?|[A-Za-z0-9]|[A-Za]|[0-9])+( +)?' + args.separator + '( +)?', '', line, 1),
                    recipients=args.resident, always_trust=True)
                if not encrypted_value.ok:
                    print(encrypted_value.status, '\n', encrypted_value.stderr)
                    break
                line = re.sub(r'^( +)?', '', line)
                encrypted_result.write('        ' + re.sub(r'( +)?' + args.separator + '.*', ': |', line))
                encrypted_result.write(re.sub(re.compile(r'^', re.MULTILINE), '            ', encrypted_value.data.decode())+'\n')

Поэтому идея состоит в том, чтобы иметь возможность указать, в каком вложенном блоке в yaml я хочу добавить данные, чтобы сделать скрипт более гибким.

+1
источник поделиться
1 ответ

Вы можете использовать низкоуровневый интерфейс событий PyYAML. Предполагая, что у вас есть входной файл YAML и вы хотите записать изменения в выходной файл YAML, вы можете написать функцию, которая проходит через генерируемый поток событий PyYAML, и вставляет запрошенные дополнительные значения в указанные местоположения:

import yaml
from yaml.events import *

class AppendableEvents:
  def __init__(self, path, events):
    self.path = path
    self.events = events

  def correct_position(self, levels):
    if len(self.path) != len(levels):
      return False
    for index, expected in enumerate(self.path):
      if expected != levels[index].cur_id:
        return False
    return True

class Level:
  def __init__(self, mode):
    self.mode = mode
    self.cur_id = -1 if mode == "item" else ""

def append_to_yaml(yamlFile, targetFile, items):
  events = []
  levels = []
  with open(yamlFile, 'r') as handle:
    for event in yaml.parse(handle):
      if isinstance(event, StreamStartEvent) or \
         isinstance(event, StreamEndEvent) or \
         isinstance(event, DocumentStartEvent) or \
         isinstance(event, DocumentEndEvent):
        pass
      elif isinstance(event, CollectionStartEvent):
        if len(levels) > 0:
          if levels[-1].mode == "key":
            # we can only handle scalar keys
            raise ValueError("encountered complex key!")
          else:
            if levels[-1].mode == "value":
              levels[-1].mode = "key"
        if isinstance(event, MappingStartEvent):
          levels.append(Level("key"))
        else: # SequenceStartEvent
          levels.append(Level("item"))
      elif isinstance(event, ScalarEvent):
        if len(levels) > 0:
          if levels[-1].mode == "item":
            levels[-1].cur_id += 1
          elif levels[-1].mode == "key":
            levels[-1].cur_id = event.value
            levels[-1].mode = "value"
          else: # mode == "value"
            levels[-1].mode = "key"
      elif isinstance(event, CollectionEndEvent):
        # here we check whether we want to append anything
        levels.pop()
        for item in items:
          if item.correct_position(levels):
            for additional_event in item.events:
              events.append(additional_event)
      events.append(event)
  with open(targetFile, mode="w") as handle:
    yaml.emit(events, handle)

Чтобы использовать его, вы должны предоставить дополнительный материал, который хотите добавить в список событий YAML, и указать желаемую позицию в виде списка ключей (или индексов последовательности):

def key(name):
  return ScalarEvent(None, None, (True, True), name)

def literal_value(content):
  return ScalarEvent(None, None, (False, True), content, style="|")

append_to_yaml("file1.yaml", "file1_modified.yaml", [
  AppendableEvents(["test3", "service1"], [
    key("my-appended-key"),
    literal_value("\"my appended value\"\n")]),
  AppendableEvents(["test3"], [
    key("my_second_appended_key"),
    literal_value("\"my second appended value\"\n")])])

Этот код правильно преобразует файл file1.yaml в данный модифицированный файл. В общем, это также позволяет добавлять сложные (последовательностные или картографические) узлы. Вот основной пример, как это сделать:

def seq(*scalarValues):
  return [SequenceStartEvent(None, None, True)] + \
    [ScalarEvent(None, None, (True, False), v) for v in scalarValues] + \
    [SequenceEndEvent()]

def map(*scalarValues):
  return [MappingStartEvent(None, None, True)] + \
    [ScalarEvent(None, None, (True, False), v) for v in scalarValues] + \
    [MappingEndEvent()]

append_to_yaml("file1.yaml", "file1_modified.yaml", [
  AppendableEvents(["test3", "service1"], [
    key("my-appended-key")] + seq("one", "two", "three")),
  AppendableEvents(["test3"], [
    key("my_second_appended_key")] + map("foo", "bar"))])
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос