Luego de mucho batallar hemos dado con el arreglo de este atasco que muchos de nuestros usuarios de esta web presentan. Si deseas aportar algún detalle no dejes de aportar tu conocimiento.
Solución:
Primero, déjeme comenzar diciendo que usar yaml.Node no produce un yaml válido cuando se calcula a partir de un yaml válido, dado por el siguiente ejemplo. Probablemente debería presentar un problema.
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))
Produce el siguiente yaml no válido en la versión go1.12.3 windows / amd64
version: 1
type: verbose
kind: bfr
# my list of applications
applications:
- # First app
name: app1
kind: nodejs
path: app1
exec:
platforms: k8s
builder: test
En segundo lugar, usando una estructura como
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"`
Desde el blog de ubuntu y la documentación de origen, parecía que identificaría correctamente los campos dentro de la estructura que son nodos y construiría ese árbol por separado, pero ese no es el caso. Cuando no se ordena, dará un árbol de nodos correcto, pero cuando se vuelve a ordenar, producirá el siguiente yaml con todos los campos que expone yaml.Node. Lamentablemente no podemos seguir este camino, debemos encontrar otro camino.
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
Pasando por alto el primer problema y el error marshal para yaml.Nodes en una estructura (en gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467) ahora podemos manipular los nodos que expone el paquete. Desafortunadamente, no existe una abstracción que agregue nodos con facilidad, por lo que los usos pueden variar y la identificación de nodos puede ser una molestia. La reflexión podría ayudar un poco aquí, así que lo dejo como ejercicio para ti.
Encontrará comentarios arrojados. Volcados que descargan todo el árbol de nodos en un formato agradable, esto ayudó con la depuración al agregar nodos al árbol de origen.
Ciertamente, también puede eliminar nodos, solo tendrá que identificar qué nodos particulares deben eliminarse. Solo debe asegurarse de eliminar los nodos principales si fuera un mapa o una secuencia.
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.NodeKind: 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.NodekeyNode, 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.NodeKind: yaml.MappingNode,
Tag: "!!map",
return n1, n2
Produce 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
Te invitamos a confirmar nuestro quehacer mostrando un comentario y dejando una valoración te damos la bienvenida.