How to idiomatically fill empty fields with default values for Kubernetes API objects?

5/13/2019

I want to compare two Kubernetes API objects (e.g. v1.PodSpecs): one of them was created manually (expected state), the other one was received from the Kubernetes API/client (actual state). The problem is that even if the two objects are semantically equal, the manually created struct has zerovalues for unspecified fields where the other struct has default values, and so the two doesn't match. It means that a simple reflect.DeepEqual() call is not sufficient for comparison.

E.g. after this:

expected := &v1.Container{
    Name:  "busybox",
    Image: "busybox",
}

actual := getContainerSpecFromApi(...)

expected.ImagePullPolicy will be "", while actual.ImagePullPolicy will be "IfNotPresent" (the default value), so the comparison fails.

Is there an idiomatic way to replace zerovalues with default values in Kubernetes API structs specifically? Or alternatively is a constructor function that initializes the struct with default values available for them somewhere?

EDIT: Currently I am using handwritten equality tests for each K8s API object types, but this doesn't seem to be maintainable to me. I am looking for a simple (set of) function(s) that "knows" the default values for all built-in Kubernetes API object fields (maybe somewhere under k8s.io/api*?). Something like this:

expected = api.ApplyContainerDefaults(expected)
if !reflect.DeepEqual(expected, actual) {
    reconcile(expected, actual)
}
-- kispaljr
client-go
go
kubernetes

1 Answer

5/17/2019

There are helpers to fill in default values in place of empty/zero ones.

Look at the SetObjectDefaults_Deployment for Deployment for instance.

Looks like the proper way to call it is via (*runtime.Scheme).Default. Below is the snippet to show the general idea:

import (
    "reflect"

    appsv1 "k8s.io/api/apps/v1"
    "k8s.io/client-go/kubernetes/scheme"
)

func compare() {
    scheme := scheme.Scheme

    // fetch the existing &appsv1.Deployment via API
    actual := ...
    expected := &appsv1.Deployment{}

    // fill in the fields to generate your expected state
    // ...

    scheme.Default(expected)
    // now you should have your empty values filled in
    if !reflect.DeepEqual(expected.Spec, actual.Spec) {
        reconcile(expected, actual)
    }
}

If you need less strict comparison for instance if you need to tolerate some injected containers then something more relaxed should be used like this.

-- Nick Revin
Source: StackOverflow