Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing ObjectMeta Regardless of Kind

Tags:

go

kubernetes

TL;DR: How can I flexibly decode a k8s API object and inspect its top-level metav1.ObjectMeta struct without knowing the object's Kind in advance?


I'm writing an admission controller endpoint that unmarshals a metav1.AdmissionReview object's Request.Object.Raw field into a concrete object based on the Request.Kind field - e.g.

if kind == "Pod" {
    var pod core.Pod
    // ...
    if _, _, err := deserializer.Decode(admissionReview.Request.Object.Raw, nil, &pod); err != nil {
        return nil, err
    }

    annotations := pod.ObjectMeta.Annotations
    // inspect/validate the annotations...

This requires knowing all possible types up front, or perhaps asking a user to supply a map[kind]corev1.Object that we can use to be more flexible.

What I'd like to instead achieve is something closer to:

var objMeta core.ObjectMeta
if _, _, err := deserializer.Decode(admissionReview.Request.Object.Raw, nil, &objMeta); err != nil {
        return nil, err
}

// if objMeta is populated, validate the fields, else
// assume it is an object that does not define an ObjectMeta
// as part of its schema.

Is this possible? The k8s API surface is fairly extensive, and I've crawled through the metav1 godoc, corev1 godoc & https://cs.k8s.io for prior art without a decent example.

The closest I've found is possibly the ObjectMetaAccessor interface, but I'd need to get from an AdmissionReview.Request.Object (type runtime.RawExtension) to a runtime.Object first.

like image 860
elithrar Avatar asked Jun 26 '19 05:06

elithrar


Video Answer


2 Answers

I believe you can't find what you are looking for because, when decoding an object, Kubernetes uses GetObjectKind and compares the result to a Scheme to convert the object to a concrete type, rather than using some generic like approach and interacting with the fields of an object without knowing it's concrete type.

So you can use reflection instead, something like:

k8sObjValue := reflect.ValueOf(admissionReview.Request.Object.Raw).Elem()
k8sObjObjectMeta := k8sObjValue.FieldByName("ObjectMeta")
annotations, ok := k8sObjObjectMeta.FieldByName("Annotations").Interface().(map[string]string)
if !ok {
    panic("failed to retrieve annotations")
}

EDIT:

Or closer to your requirements, convert to an ObjectMeta object

k8sObjValue := reflect.ValueOf(admissionReview.Request.Object.Raw).Elem()
objMeta, ok := k8sObjValue.FieldByName("ObjectMeta").Interface().(core.ObjectMeta)
if !ok {
    panic("failed to retrieve object metadata")
}
like image 169
dippynark Avatar answered Oct 23 '22 23:10

dippynark


There is a way to do this that I discovered recently, let me describe it here:

Quick disclaimer: I used admission/v1 and never tested with admission/v1beta1, which should work identically.

The data type of admissionReview.Request.Object is runtime.RawExtension, and the k8s.io/apimachinery/pkg/runtime provides a method that can convert the runtime.RawExtension to a runtime.Object. The method is called runtime.Convert_runtime_RawExtension_To_runtime_Object(...). From there, you can easily convert to the unstructured.Unstructured data type, which has all the fields from the MetaV1 object accessible with simple getter methods.

Here is a code snippet that lets you get this accomplished:

import (
    // ...
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime"
    // ...
)

// ...

func dummyFunc(ar *v1.AdmissionReview) {
    // ...
    var obj runtime.Object
    var scope conversion.Scope // While not actually used within the function, need to pass in
    err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&ar.Request.Object, &obj, scope)
    if err != nil {
        // ...
    }

    innerObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
    if err != nil {
        // ...
    }
    u := unstructured.Unstructured{Object: innerObj}
    // Now the `u` variable has all the meta info available with simple getters.
    // Sample:
    labels := u.GetLabels()
    kind := u.GetKind()
    // etc.
    // ...
}

References:

  1. https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
  2. https://godoc.org/k8s.io/apimachinery/pkg/runtime#Convert_runtime_RawExtension_To_runtime_Object
  3. https://godoc.org/k8s.io/api/admission/v1#AdmissionRequest
like image 2
ArseniyKD Avatar answered Oct 23 '22 23:10

ArseniyKD