Update specific yaml property

Issue

Im trying to parse a yaml file and update only one property,
the problem is that the type is RAW and when I update one field it update the whole object,

what I want is to update only NatIPNames from test1 to test2,
but without changing the value of minPortsPerVM(10000) How can I do it?

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 the code

package main

import (
    "encoding/json"
    "fmt"
    gcpv1alpha1 "github.com/gardener/gardener-extension-provider-gcp/pkg/apis/gcp/v1alpha1"
    "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    "io/ioutil"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/util/yaml"
)

func main() {
    shoot, e := parseShoot("test.yaml")
    if e != nil {
        fmt.Println(e)
    }

    shoot.Spec.Provider.InfrastructureConfig.Raw = encode(&gcpv1alpha1.InfrastructureConfig{
        Networks: gcpv1alpha1.NetworkConfig{
            CloudNAT: &gcpv1alpha1.CloudNAT{
                //MinPortsPerVM: ,
                NatIPNames: []gcpv1alpha1.NatIPName{
                    {Name: "test2"},
                },
            },
        },
    })
    fmt.Println(shoot.Spec.Provider.InfrastructureConfig)
}

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
}

func encode(obj runtime.Object) []byte {
    data, _ := json.Marshal(obj)
    return data
}

Solution

I am not well aware of the gardener properties. But what you can do is create a struct for the InfrastructureConfig like below

type InfraConfig struct {
    APIVersion string `json:"apiVersion"`
    Kind       string `json:"kind"`
    Networks   struct {
        CloudNAT struct {
            MinPortsPerVM int `json:"minPortsPerVM"`
            NatIPNames    []struct {
                Name string `json:"name"`
            } `json:"natIPNames"`
        } `json:"cloudNAT"`
    } `json:"networks"`
}

and create a variable referencing that struct and unmarshal the Raw object into that like below.

    var existingInfraConfig InfraConfig
    err := json.Unmarshal(shoot.Spec.Provider.InfrastructureConfig.Raw, &existingInfraConfig)

then you can edit on the name (you might want to add some logic to validate the slice to update the right index) and marshal it and assign it back to the InfraConfig like below.

    existingInfraConfig.Networks.CloudNAT.NatIPNames[0].Name = "test2"
    byteData, _ := json.Marshal(existingInfraConfig)

    shoot.Spec.Provider.InfrastructureConfig = &runtime.RawExtension{
        Raw:    byteData,
        Object: nil,
    }

On the whole, it would be like

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    "io/ioutil"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/util/yaml"
)

type InfraConfig struct {
    APIVersion string `json:"apiVersion"`
    Kind       string `json:"kind"`
    Networks   struct {
        CloudNAT struct {
            MinPortsPerVM int `json:"minPortsPerVM"`
            NatIPNames    []struct {
                Name string `json:"name"`
            } `json:"natIPNames"`
        } `json:"cloudNAT"`
    } `json:"networks"`
}

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))

}

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
}


Answered By – gkarthiks

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.