mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
Merge pull request #78914 from liggitt/pod-spec-defaults
Add tests to detect default changes to podspec and podspectemplate defaults
This commit is contained in:
commit
9c8827f564
@ -17,14 +17,17 @@ limitations under the License.
|
|||||||
package v1_test
|
package v1_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||||
@ -34,6 +37,279 @@ import (
|
|||||||
_ "k8s.io/kubernetes/pkg/api/testapi"
|
_ "k8s.io/kubernetes/pkg/api/testapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestWorkloadDefaults detects changes to defaults within PodTemplateSpec.
|
||||||
|
// Defaulting changes within this type can cause spurious rollouts of workloads on API server update.
|
||||||
|
func TestWorkloadDefaults(t *testing.T) {
|
||||||
|
rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}}
|
||||||
|
template := rc.Spec.Template
|
||||||
|
// New defaults under PodTemplateSpec are only acceptable if they would not be applied when reading data from a previous release.
|
||||||
|
// Forbidden: adding a new field `MyField *bool` and defaulting it to a non-nil value
|
||||||
|
// Forbidden: defaulting an existing field `MyField *bool` when it was previously not defaulted
|
||||||
|
// Forbidden: changing an existing default value
|
||||||
|
// Allowed: adding a new field `MyContainer *MyType` and defaulting a child of that type (e.g. `MyContainer.MyChildField`) if and only if MyContainer is non-nil
|
||||||
|
expectedDefaults := map[string]string{
|
||||||
|
".Spec.Containers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Containers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.Containers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.Containers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
||||||
|
".Spec.DNSPolicy": `"ClusterFirst"`,
|
||||||
|
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
||||||
|
".Spec.RestartPolicy": `"Always"`,
|
||||||
|
".Spec.SchedulerName": `"default-scheduler"`,
|
||||||
|
".Spec.SecurityContext": `{}`,
|
||||||
|
".Spec.TerminationGracePeriodSeconds": `30`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.CachingMode": `"ReadWrite"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.FSType": `"ext4"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.Kind": `"Shared"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.ReadOnly": `false`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ConfigMap.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].ServiceAccountToken.ExpirationSeconds": `3600`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.Keyring": `"/etc/ceph/keyring"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.RBDPool": `"rbd"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.RadosUser": `"admin"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ScaleIO.FSType": `"xfs"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`,
|
||||||
|
}
|
||||||
|
defaults := detectDefaults(t, rc, reflect.ValueOf(template))
|
||||||
|
if !reflect.DeepEqual(expectedDefaults, defaults) {
|
||||||
|
t.Errorf("Defaults for PodTemplateSpec changed. This can cause spurious rollouts of workloads on API server upgrade.")
|
||||||
|
t.Logf(diff.ObjectReflectDiff(expectedDefaults, defaults))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPodDefaults detects changes to defaults within PodSpec.
|
||||||
|
// Defaulting changes within this type (*especially* within containers) can cause kubelets to restart containers on API server update.
|
||||||
|
func TestPodDefaults(t *testing.T) {
|
||||||
|
pod := &v1.Pod{}
|
||||||
|
// New defaults under PodSpec are only acceptable if they would not be applied when reading data from a previous release.
|
||||||
|
// Forbidden: adding a new field `MyField *bool` and defaulting it to a non-nil value
|
||||||
|
// Forbidden: defaulting an existing field `MyField *bool` when it was previously not defaulted
|
||||||
|
// Forbidden: changing an existing default value
|
||||||
|
// Allowed: adding a new field `MyContainer *MyType` and defaulting a child of that type (e.g. `MyContainer.MyChildField`) if and only if MyContainer is non-nil
|
||||||
|
expectedDefaults := map[string]string{
|
||||||
|
".Spec.Containers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Containers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.Containers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.Containers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.Containers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.Containers[0].Resources.Requests": `{"":"0"}`, // this gets defaulted from the limits field
|
||||||
|
".Spec.Containers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
||||||
|
".Spec.DNSPolicy": `"ClusterFirst"`,
|
||||||
|
".Spec.EnableServiceLinks": `true`,
|
||||||
|
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Resources.Requests": `{"":"0"}`, // this gets defaulted from the limits field
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
||||||
|
".Spec.RestartPolicy": `"Always"`,
|
||||||
|
".Spec.SchedulerName": `"default-scheduler"`,
|
||||||
|
".Spec.SecurityContext": `{}`,
|
||||||
|
".Spec.TerminationGracePeriodSeconds": `30`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.CachingMode": `"ReadWrite"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.FSType": `"ext4"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.Kind": `"Shared"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.AzureDisk.ReadOnly": `false`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ConfigMap.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].ServiceAccountToken.ExpirationSeconds": `3600`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.Keyring": `"/etc/ceph/keyring"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.RBDPool": `"rbd"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.RBD.RadosUser": `"admin"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ScaleIO.FSType": `"xfs"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`,
|
||||||
|
".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`,
|
||||||
|
}
|
||||||
|
defaults := detectDefaults(t, pod, reflect.ValueOf(pod))
|
||||||
|
if !reflect.DeepEqual(expectedDefaults, defaults) {
|
||||||
|
t.Errorf("Defaults for PodSpec changed. This can cause spurious restarts of containers on API server upgrade.")
|
||||||
|
t.Logf(diff.ObjectReflectDiff(expectedDefaults, defaults))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testPath struct {
|
||||||
|
path string
|
||||||
|
value reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectDefaults(t *testing.T, obj runtime.Object, v reflect.Value) map[string]string {
|
||||||
|
defaults := map[string]string{}
|
||||||
|
toVisit := []testPath{{path: "", value: v}}
|
||||||
|
|
||||||
|
for len(toVisit) > 0 {
|
||||||
|
visit := toVisit[0]
|
||||||
|
toVisit = toVisit[1:]
|
||||||
|
|
||||||
|
legacyscheme.Scheme.Default(obj)
|
||||||
|
defaultedV := visit.value
|
||||||
|
zeroV := reflect.Zero(visit.value.Type())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case visit.value.Kind() == reflect.Struct:
|
||||||
|
for fi := 0; fi < visit.value.NumField(); fi++ {
|
||||||
|
structField := visit.value.Type().Field(fi)
|
||||||
|
valueField := visit.value.Field(fi)
|
||||||
|
if valueField.CanSet() {
|
||||||
|
toVisit = append(toVisit, testPath{path: visit.path + "." + structField.Name, value: valueField})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case visit.value.Kind() == reflect.Slice:
|
||||||
|
if !visit.value.IsNil() {
|
||||||
|
// if we already have a value, we got defaulted
|
||||||
|
marshaled, _ := json.Marshal(defaultedV.Interface())
|
||||||
|
defaults[visit.path] = string(marshaled)
|
||||||
|
} else if visit.value.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
if strings.HasPrefix(visit.path, ".ObjectMeta.ManagedFields[") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if we don't already have a value, and contain structs, add an empty item so we can recurse
|
||||||
|
item := reflect.New(visit.value.Type().Elem()).Elem()
|
||||||
|
visit.value.Set(reflect.Append(visit.value, item))
|
||||||
|
toVisit = append(toVisit, testPath{path: visit.path + "[0]", value: visit.value.Index(0)})
|
||||||
|
} else if !isPrimitive(visit.value.Type().Elem().Kind()) {
|
||||||
|
t.Logf("unhandled non-primitive slice type %s: %s", visit.path, visit.value.Type().Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
case visit.value.Kind() == reflect.Map:
|
||||||
|
if !visit.value.IsNil() {
|
||||||
|
// if we already have a value, we got defaulted
|
||||||
|
marshaled, _ := json.Marshal(defaultedV.Interface())
|
||||||
|
defaults[visit.path] = string(marshaled)
|
||||||
|
} else if visit.value.Type().Key().Kind() == reflect.String && visit.value.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
if strings.HasPrefix(visit.path, ".ObjectMeta.ManagedFields[") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if we don't already have a value, and contain structs, add an empty item so we can recurse
|
||||||
|
item := reflect.New(visit.value.Type().Elem()).Elem()
|
||||||
|
visit.value.Set(reflect.MakeMap(visit.value.Type()))
|
||||||
|
visit.value.SetMapIndex(reflect.New(visit.value.Type().Key()).Elem(), item)
|
||||||
|
toVisit = append(toVisit, testPath{path: visit.path + "[*]", value: item})
|
||||||
|
} else if !isPrimitive(visit.value.Type().Elem().Kind()) {
|
||||||
|
t.Logf("unhandled non-primitive map type %s: %s", visit.path, visit.value.Type().Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
case visit.value.Kind() == reflect.Ptr:
|
||||||
|
if visit.value.IsNil() {
|
||||||
|
if visit.value.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
visit.value.Set(reflect.New(visit.value.Type().Elem()))
|
||||||
|
toVisit = append(toVisit, testPath{path: visit.path, value: visit.value.Elem()})
|
||||||
|
} else if !isPrimitive(visit.value.Type().Elem().Kind()) {
|
||||||
|
t.Errorf("unhandled non-primitive nil ptr: %s: %s", visit.path, visit.value.Type())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if visit.path != "" {
|
||||||
|
marshaled, _ := json.Marshal(defaultedV.Interface())
|
||||||
|
defaults[visit.path] = string(marshaled)
|
||||||
|
}
|
||||||
|
toVisit = append(toVisit, testPath{path: visit.path, value: visit.value.Elem()})
|
||||||
|
}
|
||||||
|
|
||||||
|
case isPrimitive(visit.value.Kind()):
|
||||||
|
if !reflect.DeepEqual(defaultedV.Interface(), zeroV.Interface()) {
|
||||||
|
marshaled, _ := json.Marshal(defaultedV.Interface())
|
||||||
|
defaults[visit.path] = string(marshaled)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("unhandled kind: %s: %s", visit.path, visit.value.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPrimitive(k reflect.Kind) bool {
|
||||||
|
switch k {
|
||||||
|
case reflect.String, reflect.Bool, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
|
func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
|
||||||
codec := legacyscheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion)
|
codec := legacyscheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion)
|
||||||
data, err := runtime.Encode(codec, obj)
|
data, err := runtime.Encode(codec, obj)
|
||||||
|
Loading…
Reference in New Issue
Block a user