enhance webhook to set the value of taint timeAdded (#186)

Signed-off-by: Yang Le <yangle@redhat.com>
This commit is contained in:
Yang Le
2022-01-12 02:54:07 +01:00
committed by GitHub
parent 751c36b6f9
commit ffbb2b9744
15 changed files with 451 additions and 43 deletions
@@ -97,6 +97,7 @@ spec:
description: TimeAdded represents the time at which the taint was added.
type: string
format: date-time
nullable: true
value:
description: Value is the taint value corresponding to the taint key.
type: string
@@ -295,7 +295,7 @@ spec:
integer:
description: Integer is the integer value when type is integer.
type: integer
format: int32
format: int64
string:
description: String is the string value when when type is string.
type: string
+1 -1
View File
@@ -19,7 +19,7 @@ require (
k8s.io/klog/v2 v2.30.0
k8s.io/kube-aggregator v0.23.0
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade
sigs.k8s.io/controller-runtime v0.11.0
)
+2 -2
View File
@@ -1282,8 +1282,8 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2 h1:r+2z448obb2/SfLCtjKN4ge7ZTcIj2qNVWU3qp6CuMw=
open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2/go.mod h1:0IUTh8J+p4pv1THh1r9oO0luX9Z1FLDEAmvzW09qC0o=
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade h1:oPuNeeOub6iZFavW/iULi+ctyJsOjQE75Plmuyk9D9Y=
open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade/go.mod h1:0IUTh8J+p4pv1THh1r9oO0luX9Z1FLDEAmvzW09qC0o=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+14
View File
@@ -321,3 +321,17 @@ func ManagedClusterAssetFn(fs embed.FS, managedClusterName string) resourceapply
return assets.MustCreateAssetFromTemplate(name, template, config).Data, nil
}
}
// FindTaintByKey returns a taint if the managed cluster has a taint with the given key.
func FindTaintByKey(managedCluster *clusterv1.ManagedCluster, key string) *clusterv1.Taint {
if managedCluster == nil {
return nil
}
for _, taint := range managedCluster.Spec.Taints {
if key != taint.Key {
continue
}
return &taint
}
return nil
}
+54
View File
@@ -451,6 +451,60 @@ func TestCleanUpGroupFromRoleBindings(t *testing.T) {
}
}
func TestFindTaintByKey(t *testing.T) {
cases := []struct {
name string
cluster *clusterv1.ManagedCluster
key string
expected *clusterv1.Taint
}{
{
name: "nil of managed cluster",
key: "taint1",
},
{
name: "taint found",
cluster: &clusterv1.ManagedCluster{
Spec: clusterv1.ManagedClusterSpec{
Taints: []clusterv1.Taint{
{
Key: "taint1",
Value: "value1",
},
},
},
},
key: "taint1",
expected: &clusterv1.Taint{
Key: "taint1",
Value: "value1",
},
},
{
name: "taint not found",
cluster: &clusterv1.ManagedCluster{
Spec: clusterv1.ManagedClusterSpec{
Taints: []clusterv1.Taint{
{
Key: "taint1",
Value: "value1",
},
},
},
},
key: "taint2",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := FindTaintByKey(c.cluster, c.key)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("expected %v but got %v", c.expected, actual)
}
})
}
}
func getApplyFileNames(applyFiles map[string]runtime.Object) []string {
keys := []string{}
for key := range applyFiles {
+107 -6
View File
@@ -2,9 +2,13 @@ package cluster
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
clusterv1 "open-cluster-management.io/api/cluster/v1"
"open-cluster-management.io/registration/pkg/helpers"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -13,7 +17,13 @@ import (
"k8s.io/klog/v2"
)
const defaultLeaseDurationSecondsPatch = `[{"op": "replace", "path": "/spec/leaseDurationSeconds", "value": 60}]`
var nowFunc = time.Now
type jsonPatchOperation struct {
Operation string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
// ManagedClusterMutatingAdmissionHook will mutate the creating/updating managedcluster request.
type ManagedClusterMutatingAdmissionHook struct{}
@@ -58,18 +68,109 @@ func (a *ManagedClusterMutatingAdmissionHook) Admit(req *admissionv1beta1.Admiss
return status
}
// If LeaseDurationSeconds value is zero, update it to 60 by default
if managedCluster.Spec.LeaseDurationSeconds == 0 {
status.Patch = []byte(defaultLeaseDurationSecondsPatch)
pt := admissionv1beta1.PatchTypeJSONPatch
status.PatchType = &pt
var jsonPatches []jsonPatchOperation
// set timeAdded of taint if it is nil and reset it if it is modified
taintJsonPatches, status := a.processTaints(managedCluster, req.OldObject.Raw)
if !status.Allowed {
return status
}
jsonPatches = append(jsonPatches, taintJsonPatches...)
if len(jsonPatches) == 0 {
return status
}
patch, err := json.Marshal(jsonPatches)
if err != nil {
status.Allowed = false
status.Result = &metav1.Status{
Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
Message: err.Error(),
}
return status
}
status.Patch = patch
pt := admissionv1beta1.PatchTypeJSONPatch
status.PatchType = &pt
return status
}
// processTaints generates json patched for cluster taints
func (a *ManagedClusterMutatingAdmissionHook) processTaints(managedCluster *clusterv1.ManagedCluster, oldManagedClusterRaw []byte) ([]jsonPatchOperation, *admissionv1beta1.AdmissionResponse) {
status := &admissionv1beta1.AdmissionResponse{
Allowed: true,
}
if len(managedCluster.Spec.Taints) == 0 {
return nil, status
}
var oldManagedCluster *clusterv1.ManagedCluster
if len(oldManagedClusterRaw) > 0 {
cluster := &clusterv1.ManagedCluster{}
if err := json.Unmarshal(oldManagedClusterRaw, cluster); err != nil {
status.Allowed = false
status.Result = &metav1.Status{
Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
Message: err.Error(),
}
return nil, status
}
oldManagedCluster = cluster
}
var invalidTaints []string
var jsonPatches []jsonPatchOperation
now := metav1.NewTime(nowFunc())
for index, taint := range managedCluster.Spec.Taints {
originalTaint := helpers.FindTaintByKey(oldManagedCluster, taint.Key)
switch {
case originalTaint == nil:
// new taint
if !taint.TimeAdded.IsZero() {
invalidTaints = append(invalidTaints, taint.Key)
continue
}
jsonPatches = append(jsonPatches, newTaintTimeAddedJsonPatch(index, now.Time))
case originalTaint.Value == taint.Value && originalTaint.Effect == taint.Effect:
// no change
if !originalTaint.TimeAdded.Equal(&taint.TimeAdded) {
invalidTaints = append(invalidTaints, taint.Key)
}
default:
// taint's value/effect has changed
if !taint.TimeAdded.IsZero() {
invalidTaints = append(invalidTaints, taint.Key)
continue
}
jsonPatches = append(jsonPatches, newTaintTimeAddedJsonPatch(index, now.Time))
}
}
if len(invalidTaints) == 0 {
return jsonPatches, status
}
status.Allowed = false
status.Result = &metav1.Status{
Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
Message: fmt.Sprintf("It is not allowed to set TimeAdded of Taint %q.", strings.Join(invalidTaints, ",")),
}
return nil, status
}
// Initialize is called by generic-admission-server on startup to setup initialization that managedclusters webhook needs.
func (a *ManagedClusterMutatingAdmissionHook) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
// do nothing
return nil
}
func newTaintTimeAddedJsonPatch(index int, timeAdded time.Time) jsonPatchOperation {
return jsonPatchOperation{
Operation: "replace",
Path: fmt.Sprintf("/spec/taints/%d/timeAdded", index),
Value: timeAdded.UTC().Format(time.RFC3339),
}
}
+167 -26
View File
@@ -2,8 +2,12 @@ package cluster
import (
"encoding/json"
"net/http"
"reflect"
"testing"
"time"
clusterv1 "open-cluster-management.io/api/cluster/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -12,7 +16,7 @@ import (
)
func TestManagedClusterMutate(t *testing.T) {
pt := admissionv1beta1.PatchTypeJSONPatch
now := time.Now()
cases := []struct {
name string
request *admissionv1beta1.AdmissionRequest
@@ -28,9 +32,7 @@ func TestManagedClusterMutate(t *testing.T) {
Resource: "tests",
},
},
expectedResponse: &admissionv1beta1.AdmissionResponse{
Allowed: true,
},
expectedResponse: newAdmissionResponse(true).build(),
},
{
name: "mutate deleting operation",
@@ -38,54 +40,193 @@ func TestManagedClusterMutate(t *testing.T) {
Resource: managedclustersSchema,
Operation: admissionv1beta1.Delete,
},
expectedResponse: &admissionv1beta1.AdmissionResponse{
Allowed: true,
},
expectedResponse: newAdmissionResponse(true).build(),
},
{
name: "mutate a ManagedCluster without LeaseDurationSeconds setting",
name: "new taints",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
Object: newManagedClusterObj(),
},
expectedResponse: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op": "replace", "path": "/spec/leaseDurationSeconds", "value": 60}]`),
PatchType: &pt,
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, nil)).
addTaint(newTaint("c", "d", clusterv1.TaintEffectPreferNoSelect, nil)).
build(),
},
expectedResponse: newAdmissionResponse(true).
addJsonPatch(newTaintTimeAddedJsonPatch(0, now)).
addJsonPatch(newTaintTimeAddedJsonPatch(1, now)).
build(),
},
{
name: "mutate a ManagedCluster with LeaseDurationSeconds setting",
name: "new taint request denied",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
Object: newManagedClusterObjWithLeaseDurationSeconds(60),
},
expectedResponse: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, 0))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectPreferNoSelect, newTime(now, 0))).
build(),
},
expectedResponse: newAdmissionResponse(false).
withResult(metav1.StatusFailure, http.StatusBadRequest, metav1.StatusReasonBadRequest, "It is not allowed to set TimeAdded of Taint \"a,c\".").
build(),
},
{
name: "update taint",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))). // no change
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelectIfNew, nil)). // effect modified
build(),
},
expectedResponse: newAdmissionResponse(true).
addJsonPatch(newTaintTimeAddedJsonPatch(1, now)).
build(),
},
{
name: "taint update request denied",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -20*time.Second))). // timeAdded modified
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelectIfNew, newTime(now, -10*time.Second))). // effect modified with timeAdded
build(),
},
expectedResponse: newAdmissionResponse(false).
withResult(metav1.StatusFailure, http.StatusBadRequest, metav1.StatusReasonBadRequest, "It is not allowed to set TimeAdded of Taint \"a,c\".").
build(),
},
{
name: "delete taint",
request: &admissionv1beta1.AdmissionRequest{
Resource: managedclustersSchema,
Operation: admissionv1beta1.Create,
OldObject: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
addTaint(newTaint("c", "d", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
Object: newManagedCluster().
withLeaseDurationSeconds(60).
addTaint(newTaint("a", "b", clusterv1.TaintEffectNoSelect, newTime(now, -10*time.Second))).
build(),
},
expectedResponse: newAdmissionResponse(true).build(),
},
}
nowFunc = func() time.Time {
return now
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
admissionHook := &ManagedClusterMutatingAdmissionHook{}
actualResponse := admissionHook.Admit(c.request)
if !reflect.DeepEqual(actualResponse, c.expectedResponse) {
t.Errorf("expected %#v but got: %#v", c.expectedResponse.Result, actualResponse.Result)
t.Errorf("expected \n%#v but got: \n%#v", c.expectedResponse, actualResponse)
}
})
}
}
func newManagedClusterObjWithLeaseDurationSeconds(leaseDurationSeconds int32) runtime.RawExtension {
managedCluster := testinghelpers.NewManagedCluster()
managedCluster.Spec.LeaseDurationSeconds = leaseDurationSeconds
clusterObj, _ := json.Marshal(managedCluster)
type admissionResponseBuilder struct {
jsonPatchOperations []jsonPatchOperation
response admissionv1beta1.AdmissionResponse
}
func newAdmissionResponse(allowed bool) *admissionResponseBuilder {
return &admissionResponseBuilder{
response: admissionv1beta1.AdmissionResponse{
Allowed: allowed,
},
}
}
func (b *admissionResponseBuilder) addJsonPatch(jsonPatch jsonPatchOperation) *admissionResponseBuilder {
b.jsonPatchOperations = append(b.jsonPatchOperations, jsonPatch)
pt := admissionv1beta1.PatchTypeJSONPatch
b.response.PatchType = &pt
return b
}
func (b *admissionResponseBuilder) withResult(status string, code int32, reason metav1.StatusReason, message string) *admissionResponseBuilder {
b.response.Result = &metav1.Status{
Status: status,
Code: code,
Reason: reason,
Message: message,
}
return b
}
func (b *admissionResponseBuilder) build() *admissionv1beta1.AdmissionResponse {
if len(b.jsonPatchOperations) > 0 {
patch, _ := json.Marshal(b.jsonPatchOperations)
b.response.Patch = patch
}
return &b.response
}
type managedClusterBuilder struct {
cluster clusterv1.ManagedCluster
}
func newManagedCluster() *managedClusterBuilder {
return &managedClusterBuilder{
cluster: *testinghelpers.NewManagedCluster(),
}
}
func (b *managedClusterBuilder) withLeaseDurationSeconds(leaseDurationSeconds int32) *managedClusterBuilder {
b.cluster.Spec.LeaseDurationSeconds = leaseDurationSeconds
return b
}
func (b *managedClusterBuilder) addTaint(taint clusterv1.Taint) *managedClusterBuilder {
b.cluster.Spec.Taints = append(b.cluster.Spec.Taints, taint)
return b
}
func (b *managedClusterBuilder) build() runtime.RawExtension {
clusterObj, _ := json.Marshal(b.cluster)
return runtime.RawExtension{
Raw: clusterObj,
}
}
func newTaint(key, value string, effect clusterv1.TaintEffect, timeAdded *metav1.Time) clusterv1.Taint {
taint := clusterv1.Taint{
Key: key,
Value: value,
Effect: effect,
}
if timeAdded != nil {
taint.TimeAdded = *timeAdded
}
return taint
}
func newTime(time time.Time, offset time.Duration) *metav1.Time {
mt := metav1.NewTime(time.Add(offset))
return &mt
}
+78
View File
@@ -82,6 +82,64 @@ var _ = ginkgo.Describe("Admission webhook", func() {
gomega.Expect(deleteManageClusterAndRelatedNamespace(clusterName)).ToNot(gomega.HaveOccurred())
})
ginkgo.It("Should have the timeAdded for taints", func() {
clusterName := fmt.Sprintf("webhook-spoke-%s", rand.String(6))
ginkgo.By(fmt.Sprintf("create a managed cluster %q with taint", clusterName))
cluster := newManagedCluster(clusterName, false, validURL)
cluster.Spec.Taints = []clusterv1.Taint{
{
Key: "a",
Value: "b",
Effect: clusterv1.TaintEffectNoSelect,
},
}
_, err := clusterClient.ClusterV1().ManagedClusters().Create(context.TODO(), cluster, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
ginkgo.By("check if timeAdded of the taint is set automatically")
managedCluster, err := clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
taint := findTaint(managedCluster.Spec.Taints, "a", "b", clusterv1.TaintEffectNoSelect)
gomega.Expect(taint).ShouldNot(gomega.BeNil())
gomega.Expect(taint.TimeAdded.IsZero()).To(gomega.BeFalse())
ginkgo.By("chang the effect of the taint")
// sleep and make sure the update is performed 1 second later than the creation
time.Sleep(1 * time.Second)
gomega.Eventually(func() error {
managedCluster, err := clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
if err != nil {
return err
}
for index, taint := range managedCluster.Spec.Taints {
if taint.Key != "a" {
continue
}
if taint.Value != "b" {
continue
}
managedCluster.Spec.Taints[index] = clusterv1.Taint{
Key: taint.Key,
Value: taint.Value,
Effect: clusterv1.TaintEffectNoSelectIfNew,
}
}
_, err = clusterClient.ClusterV1().ManagedClusters().Update(context.TODO(), managedCluster, metav1.UpdateOptions{})
return err
}, 60*time.Second, 1*time.Second).Should(gomega.Succeed())
ginkgo.By("check if timeAdded of the taint is reset")
managedCluster, err = clusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
updatedTaint := findTaint(managedCluster.Spec.Taints, "a", "b", clusterv1.TaintEffectNoSelectIfNew)
gomega.Expect(updatedTaint).ShouldNot(gomega.BeNil())
gomega.Expect(taint.TimeAdded.Equal(&updatedTaint.TimeAdded)).To(gomega.BeFalse(),
"timeAdded of taint should be updated (before=%s, after=%s)", taint.TimeAdded.Time.String(), updatedTaint.TimeAdded.Time.String())
gomega.Expect(deleteManageClusterAndRelatedNamespace(clusterName)).ToNot(gomega.HaveOccurred())
})
ginkgo.It("Should respond bad request when creating a managed cluster with invalid external server URLs", func() {
clusterName := fmt.Sprintf("webhook-spoke-%s", rand.String(6))
ginkgo.By(fmt.Sprintf("create a managed cluster %q with an invalid external server URL %q", clusterName, invalidURL))
@@ -772,3 +830,23 @@ func cleanupClusterClient(saNamespace, saName string) error {
return nil
}
// findTaint returns the first matched taint in the given taint array. Return nil if no taint matched.
func findTaint(taints []clusterv1.Taint, key, value string, effect clusterv1.TaintEffect) *clusterv1.Taint {
for _, taint := range taints {
if len(key) != 0 && taint.Key != key {
continue
}
if len(value) != 0 && taint.Value != value {
continue
}
if len(effect) != 0 && taint.Effect != effect {
continue
}
return &taint
}
return nil
}
+1 -1
View File
@@ -1180,7 +1180,7 @@ k8s.io/utils/net
k8s.io/utils/path
k8s.io/utils/pointer
k8s.io/utils/trace
# open-cluster-management.io/api v0.5.1-0.20220104024812-246f7e36d0d2
# open-cluster-management.io/api v0.5.1-0.20220107041310-9303b90b6ade
## explicit; go 1.17
open-cluster-management.io/api/addon/v1alpha1
open-cluster-management.io/api/client/addon/clientset/versioned
@@ -97,6 +97,7 @@ spec:
description: TimeAdded represents the time at which the taint was added.
type: string
format: date-time
nullable: true
value:
description: Value is the taint value corresponding to the taint key.
type: string
+1
View File
@@ -109,6 +109,7 @@ type Taint struct {
// +required
Effect TaintEffect `json:"effect"`
// TimeAdded represents the time at which the taint was added.
// +nullable
// +required
TimeAdded metav1.Time `json:"timeAdded"`
}
@@ -295,7 +295,7 @@ spec:
integer:
description: Integer is the integer value when type is integer.
type: integer
format: int32
format: int64
string:
description: String is the string value when when type is string.
type: string
+3 -3
View File
@@ -327,15 +327,15 @@ type FieldValue struct {
// Integer is the integer value when type is integer.
// +optional
Integer int32 `json:"integer,omitempty"`
Integer *int64 `json:"integer,omitempty"`
// String is the string value when when type is string.
// +optional
String string `json:"string,omitempty"`
String *string `json:"string,omitempty"`
// Boolean is bool value when type is boolean.
// +optional
Boolean bool `json:"boolean,omitempty"`
Boolean *bool `json:"boolean,omitempty"`
}
// +kubebuilder:validation:Enum=Integer;String;Boolean
+19 -2
View File
@@ -170,7 +170,7 @@ func (in *FeedbackRule) DeepCopy() *FeedbackRule {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeedbackValue) DeepCopyInto(out *FeedbackValue) {
*out = *in
out.Value = in.Value
in.Value.DeepCopyInto(&out.Value)
return
}
@@ -187,6 +187,21 @@ func (in *FeedbackValue) DeepCopy() *FeedbackValue {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FieldValue) DeepCopyInto(out *FieldValue) {
*out = *in
if in.Integer != nil {
in, out := &in.Integer, &out.Integer
*out = new(int64)
**out = **in
}
if in.String != nil {
in, out := &in.String, &out.String
*out = new(string)
**out = **in
}
if in.Boolean != nil {
in, out := &in.Boolean, &out.Boolean
*out = new(bool)
**out = **in
}
return
}
@@ -517,7 +532,9 @@ func (in *StatusFeedbackResult) DeepCopyInto(out *StatusFeedbackResult) {
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = make([]FeedbackValue, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}