当前位置: 首页 > 面试题库 >

修改现有的Yaml文件并添加新数据和注释

白高逸
2023-03-14
问题内容

我最近看到go yaml lib具有新版本(V3)

具有节点功能(在我看来,这是一个杀手级功能:)),它可以在不更改文件结构的情况下帮助大量修改yaml

但是由于它是相当新的(从上周开始),所以我没有找到一些有用的文档和所需的 上下文 示例(添加新的对象/节点,并 不删除注释的情况下保持
文件结构不变 )等。

我需要的是操作yaml文件

例如

可以说我有这个yaml文件

version: 1
type: verbose
kind : bfr

# my list of applications
applications:
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test

现在,我有一个json对象(例如带有app2),需要将其插入到现有文件中

[

    {
        "comment: "Second app",
        "name": "app2",
        "kind": "golang",
        "path": "app2",
        "exec": {
            "platforms": "dockerh",
            "builder": "test"
        }
    }
]

我需要将其添加到第一个应用程序之后的yml文件中(应用程序是应用程序的数组)

version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test

# Second app
  - name: app2
    kind: golang
    path: app2
    exec:
      platforms: dockerh
      builder: test

是否可以从yaml文件中添加新的json对象?也删除现有的

我也找到了这个博客 https://blog.ubuntu.com/2019/04/05/api-v3-of-the-yaml-package-
for-go-is-available

这是代表对象的类型

type VTS struct {
    version string       `yaml:"version"`
    types   string       `yaml:"type"`
    kind    string       `yaml:"kind,omitempty"`
    apps    Applications `yaml:"applications,omitempty"`
}

type Applications []struct {
    Name string `yaml:"name,omitempty"`
    Kind string `yaml:"kind,omitempty"`
    Path string `yaml:"path,omitempty"`
    Exec struct {
        Platforms string `yaml:"platforms,omitempty"`
        Builder   string `yaml:"builder,omitempty"`
    } `yaml:"exec,omitempty"`
}

更新

测试wiil7200 我提供的解决方案后,我发现了2个问题

我用最后写到文件 err = ioutil.WriteFile("output.yaml", b, 0644)

和yaml输出有2个问题。

  1. 应用程序的数组从注释开始,应该从名称开始

  2. name输入后,该kind属性和之后的所有其他属性均未与name

知道如何解决那些问题吗?考虑到这个comments问题,可以说我是从其他属性而不是从json获得的(如果它使它更简单)

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test
-   # test 1
name: app2
    kind: golang
    path: app2
    exec:
        platform: dockerh
        builder: test

问题答案:

首先,让我开始说使用yaml.Node从有效yaml编组时不会产生有效的yaml,如以下示例所示。可能应该提出问题。

package main

import (
    "fmt"
    "log"

    "gopkg.in/yaml.v3"
)

var (
    sourceYaml = `version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test
`
)

func main() {
    t := yaml.Node{}

    err := yaml.Unmarshal([]byte(sourceYaml), &t)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    b, err := yaml.Marshal(&t)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(b))
}

在go版本go1.12.3 windows / amd64中产生以下无效的Yaml

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test

其次,使用诸如

type VTS struct {
    Version string       `yaml:"version" json:"version"`
    Types   string       `yaml:"type" json:"type"`
    Kind    string       `yaml:"kind,omitempty" json:"kind,omitempty"`
    Apps    yaml.Node `yaml:"applications,omitempty" json:"applications,omitempty"`
}

从ubuntu的博客和源文档中可以看出,它可以正确识别结构中的节点(分别是节点)并分别构建该树,但事实并非如此。取消编组时,它将提供正确的节点树,但是当重新编组时,它将产生以下yaml,其中包含yaml.Node公开的所有字段。可悲的是我们不能走这条路,必须找到另一条路。

version: "1"
type: verbose
kind: bfr
applications:
    kind: 2
    style: 0
    tag: '!!seq'
    value: ""
    anchor: ""
    alias: null
    content:
    -   #  First app
name: app1
        kind: nodejs
        path: app1
        exec:
            platforms: k8s
            builder: test
    headcomment: ""
    linecomment: ""
    footcomment: ""
    line: 9
    column: 3

现在,我们可以忽略结构中yaml.Nodes的第一个问题和程序错误(位于gopkg.in/yaml.v3
v3.0.0-20190409140830-cdc409dda467上),我们可以开始操作程序包公开的Nodes。不幸的是,没有可以轻松添加节点的抽象,因此用途可能会有所不同,并且标识节点可能会很麻烦。反思可能会对您有所帮助,因此我将其作为练习留给您。

您会发现注释spew.Dump以一种不错的格式转储了整个节点Tree,这有助于在将Node添加到源树时进行调试。

当然,您也可以删除节点,只需要确定需要删除的特定节点即可。您只需确保删除父节点(如果它们是图或序列)即可。

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "gopkg.in/yaml.v3"
)

var (
    sourceYaml = `version: 1
type: verbose
kind : bfr

# my list of applications
applications:

#  First app
  - name: app1
    kind: nodejs
    path: app1
    exec:
      platforms: k8s
      builder: test
`
    modifyJsonSource = `
[

    {
        "comment": "Second app",
        "name": "app2",
        "kind": "golang",
        "path": "app2",
        "exec": {
            "platforms": "dockerh",
            "builder": "test"
        }
    }
]
`
)

// VTS Need to Make Fields Public otherwise unmarshalling will not fill in the unexported fields.
type VTS struct {
    Version string       `yaml:"version" json:"version"`
    Types   string       `yaml:"type" json:"type"`
    Kind    string       `yaml:"kind,omitempty" json:"kind,omitempty"`
    Apps    Applications `yaml:"applications,omitempty" json:"applications,omitempty"`
}

type Applications []struct {
    Name string `yaml:"name,omitempty" json:"name,omitempty"`
    Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
    Path string `yaml:"path,omitempty" json:"path,omitempty"`
    Exec struct {
        Platforms string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
        Builder   string `yaml:"builder,omitempty" json:"builder,omitempty"`
    } `yaml:"exec,omitempty" json:"exec,omitempty"`
    Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}

func main() {
    t := yaml.Node{}

    err := yaml.Unmarshal([]byte(sourceYaml), &t)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    // Look for the Map Node with the seq array of items
    applicationNode := iterateNode(&t, "applications")

    // spew.Dump(iterateNode(&t, "applications"))

    var addFromJson Applications
    err = json.Unmarshal([]byte(modifyJsonSource), &addFromJson)
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    // Delete the Original Applications the following options:
    // applicationNode.Content = []*yaml.Node{}
    // deleteAllContents(applicationNode)
    deleteApplication(applicationNode, "name", "app1")


    for _, app := range addFromJson {
        // Build New Map Node for new sequences coming in from json
        mapNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}

        // Build Name, Kind, and Path Nodes
        mapNode.Content = append(mapNode.Content, buildStringNodes("name", app.Name, app.Comment)...)
        mapNode.Content = append(mapNode.Content, buildStringNodes("kind", app.Kind, "")...)
        mapNode.Content = append(mapNode.Content, buildStringNodes("path", app.Path, "")...)

        // Build the Exec Nodes and the Platform and Builder Nodes within it
        keyMapNode, keyMapValuesNode := buildMapNodes("exec")
        keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("platform", app.Exec.Platforms, "")...)
        keyMapValuesNode.Content = append(keyMapValuesNode.Content, buildStringNodes("builder", app.Exec.Builder, "")...)

        // Add to parent map Node
        mapNode.Content = append(mapNode.Content, keyMapNode, keyMapValuesNode)

        // Add to applications Node
        applicationNode.Content = append(applicationNode.Content, mapNode)
    }
    // spew.Dump(t)
    b, err := yaml.Marshal(&t)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(b))
}

// iterateNode will recursive look for the node following the identifier Node,
// as go-yaml has a node for the key and the value itself
// we want to manipulate the value Node
func iterateNode(node *yaml.Node, identifier string) *yaml.Node {
    returnNode := false
    for _, n := range node.Content {
        if n.Value == identifier {
            returnNode = true
            continue
        }
        if returnNode {
            return n
        }
        if len(n.Content) > 0 {
            ac_node := iterateNode(n, identifier)
            if ac_node != nil {
                return ac_node
            }
        }
    }
    return nil
}

// deleteAllContents will remove all the contents of a node
// Mark sure to pass the correct node in otherwise bad things will happen
func deleteAllContents(node *yaml.Node) {
    node.Content = []*yaml.Node{}
}

// deleteApplication expects that a sequence Node with all the applications are present
// if the key value are not found it will not log any errors, and return silently
// this is expecting a map like structure for the applications
func deleteApplication(node *yaml.Node, key, value string) {
    state := -1
    indexRemove := -1
    for index, parentNode := range node.Content {
        for _, childNode := range parentNode.Content {
            if key == childNode.Value && state == -1 {
                state += 1
                continue // found expected move onto next
            }
            if value == childNode.Value && state == 0 {
                state += 1
                indexRemove = index
                break // found the target exit out of the loop
            } else if state == 0 {
                state = -1
            }
        }
    }
    if state == 1 {
        // Remove node from contents
        // node.Content = append(node.Content[:indexRemove], node.Content[indexRemove+1:]...)
        // Don't Do this you might have a potential memory leak source: https://github.com/golang/go/wiki/SliceTricks
        // Since the underlying nodes are pointers
        length := len(node.Content)
        copy(node.Content[indexRemove:], node.Content[indexRemove+1:])
        node.Content[length-1] = nil
        node.Content = node.Content[:length-1]
    }
}


// buildStringNodes builds Nodes for a single key: value instance
func buildStringNodes(key, value, comment string) []*yaml.Node {
    keyNode := &yaml.Node{
        Kind:        yaml.ScalarNode,
        Tag:         "!!str",
        Value:       key,
        HeadComment: comment,
    }
    valueNode := &yaml.Node{
        Kind:  yaml.ScalarNode,
        Tag:   "!!str",
        Value: value,
    }
    return []*yaml.Node{keyNode, valueNode}
}

// buildMapNodes builds Nodes for a key: map instance
func buildMapNodes(key string) (*yaml.Node, *yaml.Node) {
    n1, n2 := &yaml.Node{
        Kind:  yaml.ScalarNode,
        Tag:   "!!str",
        Value: key,
    }, &yaml.Node{Kind: yaml.MappingNode,
        Tag: "!!map",
    }
    return n1, n2
}

生产yaml

version: 1
type: verbose
kind: bfr


# my list of applications
applications:
-   #  First app
name: app1
    kind: nodejs
    path: app1
    exec:
        platforms: k8s
        builder: test
-   # Second app
name: app2
    kind: golang
    path: app2
    exec:
        platform: dockerh
        builder: test


 类似资料:
  • 我已经解决了如何合并两个XML文件并修改匹配的属性。 我现在正在努力解决如果file1中不存在file2节点,如何添加file2节点(基于属性名) 这是我拥有的xsl文件: 我找到了如何使用XSLT合并两个xml文件,但无法解决如何将提议解决方案应用于我的xsl。有人能帮忙吗?

  • 在生产中,数据库用液基changelog.yaml更新,数据库更新成功。 我们对数据库模式进行了一些更改(作为几个增强功能的一部分),例如创建新表、更改表列和创建约束等。 有没有办法使用最新的数据库架构更改自动更新 changelog.yaml?(需要附加到现有的 YAML 文件中,而不是从 SCRACH 重新创建。 因此对生产过渡数据的影响很小)

  • 问题内容: 我想将文件/文件组添加到现有数据库中,但是我需要从变量获取路径,因为在此脚本完成后它将有所不同。当我在SQL Management Studio 2008 R2中检查脚本时,它在处返回错误。 如何使用该变量? 脚本将不会从命令行运行! 问题答案: 使用动态SQL:

  • 我想知道是否可以使用pandas函数将数据帧添加到现有的csv文件中。csv文件的结构与加载的数据相同。

  • 全部显示 所有宿主应用程序都有一个扩展接口,以便添加和设计自定义工具栏(添加内置按钮、添加作为按钮的宏,甚至给工具栏添加弹出式控件)。用 Visual Basic 代码所作的设计期修改通常用于添加或修改组合框控件。而用代码对工具栏所作的修改几乎完全属于运行时间修改(改变按钮的状态、外观、功能等等)。 对工具栏作运行时间修改 在运行时间可对工具栏作多种不同的修改。其中之一是改变命令栏按钮在工具栏上的

  • 我正在和pyinstaller做斗争。每当我使用kivy GUI和。kv文件,并运行。生成后,我收到一个致命错误: 我已经尝试添加了。kv文件,以及使用--add-data的mdb和dsn文件(对于PyODBC),但我得到一个错误:。(还有更多内容——为提到的其他文件添加数据参数。) 有没有解决这个问题的方法,或者其他的方法?