Reputation: 667
Im reading a yaml file modify a specific field test1
to test2
and I want to write it back to file
The problem that the raw data is display as numbers and I got a lot empty fields (which is related to the struct but I want to avoid it,
How I make it works? I mean just update the property of the yaml file and write it back to a new file.
This is what I’ve tried
https://go.dev/play/p/Fop37gRwmjR?v=goprev
package main
import (
"encoding/json"
"fmt"
"github.com/gardener/gardener/pkg/apis/core/v1beta1"
gyaml "github.com/go-yaml/yaml/v3"
"io/ioutil"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
)
type InfraConfig struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
Networks struct {
CloudNAT struct {
MinPortsPerVM int `json:"minPortsPerVM,omitempty"`
NatIPNames []struct {
Name string `json:"name,omitempty"`
} `json:"natIPNames,omitempty"`
} `json:"cloudNAT,omitempty"`
} `json:"networks,omitempty"`
}
func main() {
shoot, e := parseShoot("test.yaml")
if e != nil {
fmt.Println(e)
}
var existingInfraConfig InfraConfig
err := json.Unmarshal(shoot.Spec.Provider.InfrastructureConfig.Raw, &existingInfraConfig)
fmt.Println(err)
existingInfraConfig.Networks.CloudNAT.NatIPNames[0].Name = "test2"
byteData, _ := json.Marshal(existingInfraConfig)
shoot.Spec.Provider.InfrastructureConfig = &runtime.RawExtension{
Raw: byteData,
Object: nil,
}
fmt.Println(string(shoot.Spec.Provider.InfrastructureConfig.Raw))
aa, e := gyaml.Marshal(shoot)
if e != nil {
fmt.Println(e)
}
ioutil.WriteFile("test2.yaml", aa, 0644)
}
func parseShoot(path string) (*v1beta1.Shoot, error) {
var shootSpec *v1beta1.Shoot
bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(bytes, &shootSpec)
if err != nil {
return nil, err
}
return shootSpec, nil
}
// this is the yaml
apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
metadata:
name: test
namespace: ns
spec:
provider:
type: aaa
infrastructureConfig:
apiVersion: gcp.provider.extensions.gardener.cloud/v1alpha1
kind: InfrastructureConfig
networks:
cloudNAT:
minPortsPerVM: 10000
natIPNames:
- name: test1
This is what I got after writing the data to a file.
typemeta:
kind: Shoot
apiversion: core.gardener.cloud/v1beta1
objectmeta:
name: test
generatename: ""
namespace: ns
selflink: ""
uid: ""
resourceversion: ""
generation: 0
creationtimestamp: "0001-01-01T00:00:00Z"
deletiontimestamp: null
deletiongraceperiodseconds: null
labels: {}
annotations: {}
ownerreferences: []
finalizers: []
clustername: ""
managedfields: []
spec:
addons: null
cloudprofilename: ""
dns: null
extensions: []
hibernation: null
kubernetes:
allowprivilegedcontainers: null
clusterautoscaler: null
kubeapiserver: null
kubecontrollermanager: null
kubescheduler: null
kubeproxy: null
kubelet: null
version: ""
verticalpodautoscaler: null
networking:
type: ""
providerconfig: null
pods: null
nodes: null
services: null
maintenance: null
monitoring: null
provider:
type: aaa
controlplaneconfig: null
infrastructureconfig:
raw:
- 123
- 34
- 97
- 112
- 105
- 86
- 101
- 114
- 115
- 105
- 111
- 110
- 34
- 58
- 34
- 103
- 99
- 112
- 46
- 112
- 114
- 111
- 118
- 105
- 100
- 101
- 114
- 46
- 101
- 120
- 116
- 101
- 110
- 115
- 105
- 111
- 110
- 115
- 46
- 103
- 97
- 114
- 100
- 101
- 110
- 101
- 114
- 46
- 99
- 108
- 111
- 117
- 100
- 47
- 118
- 49
- 97
- 108
- 112
- 104
- 97
- 49
- 34
- 44
- 34
- 107
- 105
- 110
- 100
- 34
- 58
- 34
- 73
- 110
- 102
- 114
- 97
- 115
- 116
- 114
- 117
- 99
- 116
- 117
- 114
- 101
- 67
- 111
- 110
- 102
- 105
- 103
- 34
- 44
- 34
- 110
- 101
- 116
- 119
- 111
- 114
- 107
- 115
- 34
- 58
- 123
- 34
- 99
- 108
- 111
- 117
- 100
- 78
- 65
- 84
- 34
- 58
- 123
- 34
- 109
- 105
- 110
- 80
- 111
- 114
- 116
- 115
- 80
- 101
- 114
- 86
- 77
- 34
- 58
- 49
- 48
- 48
- 48
- 48
- 44
- 34
- 110
- 97
- 116
- 73
- 80
- 78
- 97
- 109
- 101
- 115
- 34
- 58
- 91
- 123
- 34
- 110
- 97
- 109
- 101
- 34
- 58
- 34
- 116
- 101
- 115
- 116
- 51
- 34
- 125
- 93
- 125
- 125
- 125
object: null
workers: []
purpose: null
region: ""
secretbindingname: ""
seedname: null
seedselector: null
resources: []
tolerations: []
exposureclassname: null
systemcomponents: null
status:
conditions: []
constraints: []
gardener:
id: ""
name: ""
version: ""
ishibernated: false
lastoperation: null
lasterrors: []
observedgeneration: 0
retrycyclestarttime: null
seedname: null
technicalid: ""
uid: ""
clusteridentity: null
advertisedaddresses: []
migrationstarttime: null
credentials: null
UPDATE
This is the complete file
apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
metadata:
name: test
namespace: ns
spec:
provider:
type: aaa
infrastructureConfig:
apiVersion: gcp.provider.extensions.gardener.cloud/v1alpha1
kind: InfrastructureConfig
networks:
cloudNAT:
minPortsPerVM: 10000
natIPNames:
- name: test1
Upvotes: 3
Views: 1678
Reputation: 39678
Here's minimal code to show how using yaml.Node
generally works:
package main
import (
"fmt"
"strconv"
"gopkg.in/yaml.v3"
)
var input = []byte(`
spec:
provider:
infrastructureConfig:
networks:
cloudNAT:
natIPNames:
- name: test
`)
func set(root *yaml.Node, path []string, value yaml.Node) {
if len(path) == 0 {
*root = value
return
}
key := path[0]
rest := path[1:]
switch root.Kind {
case yaml.DocumentNode:
set(root.Content[0], path, value)
case yaml.MappingNode:
for i := 0; i < len(root.Content); i += 2 {
if root.Content[i].Value == key {
set(root.Content[i+1], rest, value)
return
}
}
case yaml.SequenceNode:
index, err := strconv.Atoi(key)
if err != nil {
panic(err)
}
set(root.Content[index], rest, value)
}
}
func main() {
var doc yaml.Node
if err := yaml.Unmarshal(input, &doc); err != nil {
panic(err)
}
newVal := yaml.Node{
Kind: yaml.ScalarNode,
Value: "test2",
}
set(&doc, []string{"spec", "provider", "infrastructureConfig", "networks", "cloudNAT", "natIPNames", "0", "name"}, newVal)
out, err := yaml.Marshal(doc.Content[0])
if err != nil {
panic(err)
}
fmt.Println(string(out))
}
This code is dangerous since it ignores lots of invalid and error cases. A completely robust implementation is beyond an SO answer.
Upvotes: 2