How to deserialize Kubernetes YAML file - go

How can I deserialize a Kubernetes YAML file into an Go struct? I took a look into the kubectl code, but somehow I get an error for every YAML file:
no kind "Deployment" is registered for version "apps/v1beta1"
This is an MWE:
package main
import (
"fmt"
"k8s.io/client-go/pkg/api"
)
var service = `
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
`
func main() {
decode := api.Codecs.UniversalDecoder().Decode
//decode := api.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(service), nil, nil)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", obj)
}
I am using client version 2.0.0. The glide.yaml looks like this:
package: test/stackoverflow
import:
- package: k8s.io/client-go
version: ^2.0.0
These are the references to kubectl:
https://github.com/kubernetes/kubernetes/blob/43ac38e29e6ecf83e78bc7c5d9f804310b051c95/pkg/kubectl/cmd/apply.go#L637
https://github.com/kubernetes/kubernetes/blob/43ac38e29e6ecf83e78bc7c5d9f804310b051c95/pkg/kubectl/cmd/util/factory_client_access.go#L205-L213
Unfortunately, the docs are very confusing to me, so I have no idea how to tackle this problem.
Edit:
This problem also exists with other resource types:
no kind "Service" is registered for version "v1"

You need to import _ "k8s.io/client-go/pkg/apis/extensions/install" otherwise the schema is empty, see also docs.
The complete working example is:
$ go get -u github.com/golang/dep/cmd/dep
$ dep init
$ go run main.go
With the following main.go:
package main
import (
"fmt"
"k8s.io/client-go/pkg/api"
_ "k8s.io/client-go/pkg/api/install"
_ "k8s.io/client-go/pkg/apis/extensions/install"
)
var deployment = `
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
`
func main() {
// decode := api.Codecs.UniversalDecoder().Decode
decode := api.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(deployment), nil, nil)
if err != nil {
fmt.Printf("%#v", err)
}
fmt.Printf("%#v\n", obj)
}
Note that I also imported _ "k8s.io/client-go/pkg/api/install" for you so that you can use objects in v1 such as pods or services.
EDIT: Kudos to my colleague Stefan Schimanski who proposed the initial solution.

I've been using api machinery'sk8s.io/apimachinery/pkg/util/yaml to decode kubernete's deployment and service manifests.
import (
"fmt"
"bytes"
appsv1 "k8s.io/api/apps/v1"
k8Yaml "k8s.io/apimachinery/pkg/util/yaml"
)
...
d := &appsv1.Deployment{}
dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(deploymentManifest)), 1000)
if err := dec.Decode(&d); err != nil {
return nil, err
}
fmt.Printf("%+v", d)

import (
"fmt"
"gopkg.in/yaml.v2"
"log"
//corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/client-go/kubernetes/scheme"
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
....
func ParseYaml2(yaml []byte) (v1alpha1.Application, error) {
// Create a YAML serializer. JSON is a subset of YAML, so is supported too.
s := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme,
scheme.Scheme)
// Decode the YAML to an object.
var app v1alpha1.Application
_, _, err := s.Decode(yaml, nil, &app)
if err != nil {
panic(err)
}
//fmt.Printf("%#v\n", app)
return app, err
}
---
go.mod
// https://github.com/argoproj/argo-cd/issues/4055
replace github.com/argoproj/argo-cd => github.com/argoproj/argo-cd v1.5.5
var yaml2 = []byte(`
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
...
var app v1alpha1.Application
app,err := ParseYaml2(yaml2)
// Types from https://github.com/argoproj/argo-cd/blob/master/pkg/apis/application/v1alpha1/types.go
fmt.Printf("--- t:\n%s\n\n", app.Spec.Source.Path)
fmt.Printf("--- t:\n%s\n\n", app.Spec.Source.Helm.ValueFiles)
----

Related

k6 - create custom resource using go client

Anyone know how to create a Custom Resource using go-client. basically equivalent of kubectl apply -f 'yaml path'
apiVersion: k6.io/v1alpha1
kind: K6
metadata:
name: k6-sample
spec:
parallelism: 1
#arguments: --out statsd
#cleanup: post
script:
configMap:
name: "staging-stress-test"
file: "staging.js"
Have a go-client code to generate a Custom Resource below
func createk6CR(clientset *kubernetes.Clientset) (string, error) {
k6plugin := &v1alpha1.K6{
TypeMeta: metav1.TypeMeta{
APIVersion: "k6.io/v1alpha1",
Kind: "K6",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k6-sample-1",
Namespace: "default",
},
Spec: v1alpha1.K6Spec{
Parallelism: 3,
Script: v1alpha1.K6Script{
ConfigMap: v1alpha1.K6Configmap{
Name: "staging-stress-test",
File: "staging.js",
},
},
},
// Status: v1alpha1.K6Status{
// Stage: "started",
// },
}
body, err := json.Marshal(k6plugin)
if err != nil {
fmt.Printf("error getting Kubernetes config: %v\n", err)
os.Exit(1)
}
data, err := clientset.RESTClient().
Post().
AbsPath("/apis/k6.io/v1alpha1/namespaces/default/k6").
Body(body).
DoRaw(context.TODO())
if data != nil {
str := string(data[:])
fmt.Printf("return data: %v\n", str)
//os.Exit(1)
}
return "success", err
}
But I get Page 404 not found on AbsPath("/apis/k6.io/v1alpha1/namespaces/default/k6").
Found what was wrong with it, when you do kubectl apply pass it with -v 8 to see the Abspath check for POST
kubectl apply -f 'resource path'
I0816 09:20:56.239402 15535 round_trippers.go:463] POST https://0.0.0.0:43117/apis/k6.io/v1alpha1/namespaces/default/k6s?fieldManager=kubectl-client-side-apply&fieldValidation=Strict
I0816 09:20:56.239428 15535 round_trippers.go:469] Request Headers:
so the code with corrected AbsPath should be below,
data, err := clientset.RESTClient().
Post().
AbsPath("/apis/k6.io/v1alpha1/namespaces/default/k6s").
Body(body).
DoRaw(context.TODO())

How to use dapr nats io jetstream with Go

I am using dapr ,to use its pub sub configuration and using dapr go sdk to publish the messages .
With redis its working fine ,but when I am using nats.io jetstream its giving me error stream doesn't exists which ,even I am creating stream too
pubsub.yml I am using to run the dapr side car
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
namespace: default
spec:
type: pubsub.jetstream
version: v1
metadata:
- name: natsURL
value: "nats://localhost:4222"
- name: name
value: "connection name"
- name: durableName
value: "consumer durable name"
- name: queueGroupName
value: "queue group name"
- name: startSequence
value: 1
- name: startTime # in Unix format
value: 1630349391
- name: deliverAll
value: false
- name: flowControl
value: false
And this is the go code
//dependencies
import (
"context"
"log"
"math/rand"
"time"
"strconv"
dapr "github.com/dapr/go-sdk/client"
)
//code
var (
PUBSUB_NAME = "order_pub_sub"
TOPIC_NAME = "orders"
)
func main() {
for i := 0; i < 10; i++ {
time.Sleep(5000)
orderId := rand.Intn(1000-1) + 1
client, err := dapr.NewClient()
if err != nil {
panic(err)
}
defer client.Close()
ctx := context.Background()
//Using Dapr SDK to publish a topic
if err := client.PublishEvent(ctx, PUBSUB_NAME, TOPIC_NAME, []byte(strconv.Itoa(orderId)));
err != nil {
panic(err)
}
log.Println("Published data: " + strconv.Itoa(orderId))
}
}
I just want to know what will be the relevant names in nats ,like for topic and pubsub_name ,Or anyone able can help ?

Not able to fetch namespace, nodes & pods data from Alibaba cluster

I have created a cluster on Alibaba.
I need to fetch cluster data in the Golang project.
Getting below error from API:
{
"Code": 403,
"Message": "namespaces is forbidden: User \"281247226166595041\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope"
}
Tried accessing it via kubectl:
$ kubectl get namespace
Error from server (Forbidden): namespaces is forbidden: User "225396037912844073" cannot list resource "namespaces" in API group "" at the cluster scope
Not able to fetch data for cluster created by another user.
Please help me with this issue.
That's issue of authentication
Your user doesn't have access to list that namespace.
You need to update the RBAC or user access, or however, you are authenticating the Go client into Kubernetes.
If you are using the service account to give access check the RBAC.
Give service account cluster-admin access
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: <new-account-name>
namespace: <namespace>
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: go-rbac
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
kubectl apply -f .yaml
If you are running outside the cluster make sure your script pointing to correct kubeconfig
package main
import (
"fmt"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/tools/clientcmd"
)
func main() {
config, err := clientcmd.BuildConfigFromFlags("", <kube-config-path>)
if err != nil {
return nil, err
}
c, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
Running inside the custer
package main
import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
//
// Uncomment to load all auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth"
//
// Or uncomment to load specific auth plugins
// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)
func main() {
// creates the in-cluster config
config, err := rest.InClusterConfig()
if err != nil {
panic(err.Error())
}
// creates the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
Example : https://github.com/kubernetes/client-go/blob/master/examples/in-cluster-client-configuration/main.go

List kubernetes resources by clinet-go, How can I get the kind and apiversion?

When I get the kubernetes resources from api with client-go, but I can't found the apiversion and kind in the response, the apiversion and kind is empty. How can I get the apiversion and kind of the resource?
below is my code:
package main
import (
"flag"
"k8s.io/client-go/tools/clientcmd"
"log"
"k8s.io/client-go/kubernetes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"fmt"
)
var clientset *kubernetes.Clientset
func main() {
k8sconfig := flag.String("k8sconfig","./k8sconfig","kubernetes config file path")
flag.Parse()
config , err := clientcmd.BuildConfigFromFlags("",*k8sconfig)
if err != nil {
log.Println(err)
}
clientset , err = kubernetes.NewForConfig(config)
if err != nil {
log.Fatalln(err)
} else {
fmt.Println("connect k8s success")
}
pods,err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
if err != nil {
log.Println(err.Error())
}
for _, pod := range pods.Items{
fmt.Println("apiversion: ", pod.APIVersion, "kind: ", pod.Kind)
}
}
The output:
apiversion: kind:
apiversion: kind:
apiversion: kind:
apiversion: kind:
apiversion: kind:
apiversion: kind:
apiversion: kind:
apiversion: kind:
......
......
I think the issue is that you are getting the list of pods using the List() API so it is not a pod as you expect and doesn't have a Kind field.
You need to iterate over the list of pods to access individual pods:
for _, pod := range pods.Items {
fmt.Printf("%s %s\n", pod.GetName(), pod.GetCreationTimestamp())
}
The Kind field is present as part of Pod's Metadata and can be accessed using pod.ObjectMeta.Kind.
You are not getting the APIVersion and Kind because it was ignored from the code.
If you take a look at the API server JSON response, it will be something like below:
{
"kind":"PodList",
"apiVersion":"v1",
"metadata":{
"resourceVersion":"2397"
},
"items":[
{
... ... ...
So the response does contain the APIVersion and Kind. But when the response is decoded to k8s object, here
out, _, err := r.decoder.Decode(r.body, nil, obj)
Here you can see that the second output parameter is ignored, which is schema.GroupVersionKind.
func (c *Something) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
}
N.B.: When you are making an API call using the client-go (unless dynamic client), you already know the APIVersion (ie. CoreV1() ) and the Kind ( ie. List()).

Error while creating a pod using kubernetes client API

I'm trying to create a pod using Kubernetes client api in Go and I've been getting below error in TravisCI,
ERRO Running error: buildir: analysis skipped: errors in package: [/home/travis/gopath/src/github.com/pravarag/test-repo/check_pod.go:25:70: cannot use desiredPod (variable of type *"k8s.io/api/core/v1".Pod) as context.Context value in argument to s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create: missing method Deadline /home/travis/gopath/src/github.com/pravarag/test-repo/check_pod.go:25:80: too few arguments in call to s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create
Below is the code,
import (
"fmt"
"go.uber.org/zap"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (s *server) createPod() {
// build the pod definition
desiredPod := getPodObjet()
pod, err := s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create(desiredPod)
if err != nil {
s.log.Fatal("Failed to create the static pod", zap.Error(err))
}
fmt.Println("Created Pod: ", pod.Name)
}
func getPodObjet() *core.Pod {
pod := &core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
Labels: map[string]string{
"app": "test-pod",
},
},
Spec: core.PodSpec{
Containers: []core.Container{
{
Name: "busybox",
Image: "busybox",
ImagePullPolicy: core.PullIfNotPresent,
Command: []string{
"sleep",
"3600",
},
},
},
},
}
return pod
}
I tried to check what that error is pointing to and it seems, the actual pod Interface in K8s client code here: https://godoc.org/k8s.io/client-go/kubernetes/typed/core/v1#PodInterface
is expecting 3 arguments: one is "context.Context", "pod *v1.Pod" and "opts metav1.CreateOptions"
I tried to pass the values as:
pod, err :=s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create(context.Context, desiredPod, opts metav1.CreateOptions{})
But that doesn't work also. Even in the IDE, the code lint is pointing to missing arguments but I've seen couple of examples used to create a pod in above mentioned way that worked previously.
just use context.TODO() as argument to pass context.
try this one.
pod, err := s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create( context.TODO(), desiredPod , metav1.CreateOptions{})
here is the updated code:
import (
"fmt"
"context"
"go.uber.org/zap"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (s *server) createPod() {
// build the pod definition
desiredPod := getPodObjet()
pod, err := s.kubeClient.CoreV1().Pods(desiredPod.Namespace).Create( context.TODO(), desiredPod , metav1.CreateOptions{})
if err != nil {
s.log.Fatal("Failed to create the static pod", zap.Error(err))
}
fmt.Println("Created Pod: ", pod.Name)
}
func getPodObjet() *core.Pod {
pod := &core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
Labels: map[string]string{
"app": "test-pod",
},
},
Spec: core.PodSpec{
Containers: []core.Container{
{
Name: "busybox",
Image: "busybox",
ImagePullPolicy: core.PullIfNotPresent,
Command: []string{
"sleep",
"3600",
},
},
},
},
}
return pod
}

Resources