3

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
  • 1,942
  • 2
  • 16
  • 22
  • I believe the idiomatic approach is to have a conditional and an assignment inside to handle a true condition. That is, `if field == "" { field = defaultvalue }`. – mkopriva May 13 '19 at 15:31
  • You could of course make your life easier by using `reflect` to do it for you however I'm not sure that would be considered idiomatic. That said, if you need this mainly for testing I would definitely recommend you use `reflect`. – mkopriva May 13 '19 at 15:35
  • Hi @mkopriva! Thank you for the answer. I've realized that I wasn't clear enough, so I've edited the question to reflect that I don't want to manually set all default values for every possible API objects and for every fields, but wanted to ask if it is already implemented in the Go client for me? – kispaljr May 13 '19 at 15:38
  • If by client you mean the Go std lib, then no, there's nothing like that ready-made. If by client you mean k8s related packages then I'm afraid I don't know, I've haven't used kubernetes so far. – mkopriva May 13 '19 at 15:40
  • Yes, I meant the Kubernetes client. Sorry again for the sloppy phrasing – kispaljr May 13 '19 at 15:43
  • @VasilyAngapov I can do that, but I still would have to manually implement the logic for all problematic fields of all API objects that I use, wouldn't I? The main motivation behind the question is to avoid that – kispaljr May 13 '19 at 15:49

1 Answers1

5

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
  • 186
  • 6