Reading and writing k8s resource as yaml in golang

in #kubernetes4 years ago (edited)

I am writing this article as none other is available on this topic. The information is very scattered and often requires digging in code of k8s libraries. Let's take Q/A style for this article.

How to read k8s resource from file? The intuitive and easy way is try to use interface{} and Unmarshal yaml into it.

import (
          corev1 "k8s.io/api/core/v1"
          "io/ioutil"
)
var obj interface{}
stream, _ :=ioutils.ReadFile("pod.yaml")
yaml.Unmarshal(stream, &obj)
obj.(*corev1.Pod)

Last step will not working. obj is of type map[interface{}]interface{} resulting in conversion to Pod impossible, if multiple documents are present in file. Other than that, we haven't defined function to convert interface{} to Pod or other k8s resource.

Since, the above approach will not work let's look at how k8s deals with this. Refer client-go#issue-193.

import (
     "k8s.io/client-go/kubernetes/scheme"
      corev1 "k8s.io/api/core/v1"
)
decode := scheme.Codecs.UniversalDeserializer().Decode
stream, _ :=ioutils.ReadFile("pod.yaml")
obj, gKV, _ := decode(stream, nil, nil)
if gKV.Kind == "Pod" {
           pod := obj.(*corev1.Pod)
}

Boom! This works as it is using the codec scheme defined for k8s resource.

But this only works for native k8s resource, what if you are working with CRDs? Their codec is not directly available so we have to manually add that to scheme.

import (
     "k8s.io/client-go/kubernetes/scheme"
      corev1 "k8s.io/api/core/v1"
      apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
     "k8s.io/apimachinery/pkg/runtime/serializer"
)
sch := runtime.NewScheme()
_ = scheme.AddToScheme(sch)
_ = apiextv1beta1.AddToScheme(sch)
decode := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode
stream, _ :=ioutils.ReadFile("crd.yaml")
obj, gKV, _ := decode(stream, nil, nil)
if gKV.Kind == "CustomResourceDefinition" {
           pod := obj.(*apiextv1beta1.CustomResourceDefinition)
}

Now that we are able to Unmarshal k8s objects into proper types. How to write them back as yaml? Easy and wrong way is to directly Marshal as yaml write to a file.

import (
        "io/ioutil"
        "gopkg.in/yaml.v2"
)
w := ioutils.WriteFile("pod.yaml)
podByte, e := yaml.Marshal(&pod)
w.Write(podByte)

This will save the k8s resource to file as yaml. But it is saving runtime object with all the extra fields and types k8s cluster uses to manage them. If you try to use it, it will not work. It will look like below having `typemeta`, `objectmeta` and `selflink` which are runtime fields.

typemeta:
  kind: Pod
  apiversion: v1
objectmeta:
  selflink: ""

Solution , nothing direct. So, let's look at how kubectl prints the resource. Those aren't declaration of runtime objects.

So, after digging I found k8s.io/kubectl uses k8s.io/cli-runtime which has the printers.

import (
    "k8s.io/cli-runtime/pkg/printers"
        "io"
)
newFile, err := os.Create("pod.yaml")
y := printers.YAMLPrinter{}
defer newFile.Close()
y.PrintObj(pod, newFile) // PrintObj (runtime.Object, *io.Writer)
Sort:  

Hello elliotyagami!

Congratulations! This post has been randomly Resteemed! For a chance to get more of your content resteemed join the Steem Engine Team

Coin Marketplace

STEEM 0.27
TRX 0.13
JST 0.032
BTC 62500.50
ETH 2936.24
USDT 1.00
SBD 3.59