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.
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")
}
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With