k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/core/validation/events.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "fmt" 21 "reflect" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 eventsv1beta1 "k8s.io/api/events/v1beta1" 26 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/validation" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 "k8s.io/kubernetes/pkg/apis/core" 32 ) 33 34 const ( 35 ReportingInstanceLengthLimit = 128 36 ActionLengthLimit = 128 37 ReasonLengthLimit = 128 38 NoteLengthLimit = 1024 39 ) 40 41 func ValidateEventCreate(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList { 42 // Make sure events always pass legacy validation. 43 allErrs := legacyValidateEvent(event, requestVersion) 44 if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion { 45 // No further validation for backwards compatibility. 46 return allErrs 47 } 48 49 // Strict validation applies to creation via events.k8s.io/v1 API and newer. 50 allErrs = append(allErrs, ValidateObjectMeta(&event.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...) 51 allErrs = append(allErrs, validateV1EventSeries(event)...) 52 zeroTime := time.Time{} 53 if event.EventTime.Time == zeroTime { 54 allErrs = append(allErrs, field.Required(field.NewPath("eventTime"), "")) 55 } 56 if event.Type != v1.EventTypeNormal && event.Type != v1.EventTypeWarning { 57 allErrs = append(allErrs, field.Invalid(field.NewPath("type"), "", fmt.Sprintf("has invalid value: %v", event.Type))) 58 } 59 if event.FirstTimestamp.Time != zeroTime { 60 allErrs = append(allErrs, field.Invalid(field.NewPath("firstTimestamp"), "", "needs to be unset")) 61 } 62 if event.LastTimestamp.Time != zeroTime { 63 allErrs = append(allErrs, field.Invalid(field.NewPath("lastTimestamp"), "", "needs to be unset")) 64 } 65 if event.Count != 0 { 66 allErrs = append(allErrs, field.Invalid(field.NewPath("count"), "", "needs to be unset")) 67 } 68 if event.Source.Component != "" || event.Source.Host != "" { 69 allErrs = append(allErrs, field.Invalid(field.NewPath("source"), "", "needs to be unset")) 70 } 71 return allErrs 72 } 73 74 func ValidateEventUpdate(newEvent, oldEvent *core.Event, requestVersion schema.GroupVersion) field.ErrorList { 75 // Make sure the new event always passes legacy validation. 76 allErrs := legacyValidateEvent(newEvent, requestVersion) 77 if requestVersion == v1.SchemeGroupVersion || requestVersion == eventsv1beta1.SchemeGroupVersion { 78 // No further validation for backwards compatibility. 79 return allErrs 80 } 81 82 // Strict validation applies to update via events.k8s.io/v1 API and newer. 83 allErrs = append(allErrs, ValidateObjectMetaUpdate(&newEvent.ObjectMeta, &oldEvent.ObjectMeta, field.NewPath("metadata"))...) 84 // if the series was modified, validate the new data 85 if !reflect.DeepEqual(newEvent.Series, oldEvent.Series) { 86 allErrs = append(allErrs, validateV1EventSeries(newEvent)...) 87 } 88 89 allErrs = append(allErrs, ValidateImmutableField(newEvent.InvolvedObject, oldEvent.InvolvedObject, field.NewPath("involvedObject"))...) 90 allErrs = append(allErrs, ValidateImmutableField(newEvent.Reason, oldEvent.Reason, field.NewPath("reason"))...) 91 allErrs = append(allErrs, ValidateImmutableField(newEvent.Message, oldEvent.Message, field.NewPath("message"))...) 92 allErrs = append(allErrs, ValidateImmutableField(newEvent.Source, oldEvent.Source, field.NewPath("source"))...) 93 allErrs = append(allErrs, ValidateImmutableField(newEvent.FirstTimestamp, oldEvent.FirstTimestamp, field.NewPath("firstTimestamp"))...) 94 allErrs = append(allErrs, ValidateImmutableField(newEvent.LastTimestamp, oldEvent.LastTimestamp, field.NewPath("lastTimestamp"))...) 95 allErrs = append(allErrs, ValidateImmutableField(newEvent.Count, oldEvent.Count, field.NewPath("count"))...) 96 allErrs = append(allErrs, ValidateImmutableField(newEvent.Reason, oldEvent.Reason, field.NewPath("reason"))...) 97 allErrs = append(allErrs, ValidateImmutableField(newEvent.Type, oldEvent.Type, field.NewPath("type"))...) 98 99 // Disallow changes to eventTime greater than microsecond-level precision. 100 // Tolerating sub-microsecond changes is required to tolerate updates 101 // from clients that correctly truncate to microsecond-precision when serializing, 102 // or from clients built with incorrect nanosecond-precision protobuf serialization. 103 // See https://github.com/kubernetes/kubernetes/issues/111928 104 newTruncated := newEvent.EventTime.Truncate(time.Microsecond).UTC() 105 oldTruncated := oldEvent.EventTime.Truncate(time.Microsecond).UTC() 106 if newTruncated != oldTruncated { 107 allErrs = append(allErrs, ValidateImmutableField(newEvent.EventTime, oldEvent.EventTime, field.NewPath("eventTime"))...) 108 } 109 110 allErrs = append(allErrs, ValidateImmutableField(newEvent.Action, oldEvent.Action, field.NewPath("action"))...) 111 allErrs = append(allErrs, ValidateImmutableField(newEvent.Related, oldEvent.Related, field.NewPath("related"))...) 112 allErrs = append(allErrs, ValidateImmutableField(newEvent.ReportingController, oldEvent.ReportingController, field.NewPath("reportingController"))...) 113 allErrs = append(allErrs, ValidateImmutableField(newEvent.ReportingInstance, oldEvent.ReportingInstance, field.NewPath("reportingInstance"))...) 114 115 return allErrs 116 } 117 118 func validateV1EventSeries(event *core.Event) field.ErrorList { 119 allErrs := field.ErrorList{} 120 zeroTime := time.Time{} 121 if event.Series != nil { 122 if event.Series.Count < 2 { 123 allErrs = append(allErrs, field.Invalid(field.NewPath("series.count"), "", "should be at least 2")) 124 } 125 if event.Series.LastObservedTime.Time == zeroTime { 126 allErrs = append(allErrs, field.Required(field.NewPath("series.lastObservedTime"), "")) 127 } 128 } 129 return allErrs 130 } 131 132 // legacyValidateEvent makes sure that the event makes sense. 133 func legacyValidateEvent(event *core.Event, requestVersion schema.GroupVersion) field.ErrorList { 134 allErrs := field.ErrorList{} 135 // Because go 136 zeroTime := time.Time{} 137 138 reportingControllerFieldName := "reportingController" 139 if requestVersion == v1.SchemeGroupVersion { 140 reportingControllerFieldName = "reportingComponent" 141 } 142 143 // "New" Events need to have EventTime set, so it's validating old object. 144 if event.EventTime.Time == zeroTime { 145 // Make sure event.Namespace and the involvedInvolvedObject.Namespace agree 146 if len(event.InvolvedObject.Namespace) == 0 { 147 // event.Namespace must also be empty (or "default", for compatibility with old clients) 148 if event.Namespace != metav1.NamespaceNone && event.Namespace != metav1.NamespaceDefault { 149 allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) 150 } 151 } else { 152 // event namespace must match 153 if event.Namespace != event.InvolvedObject.Namespace { 154 allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) 155 } 156 } 157 158 } else { 159 if len(event.InvolvedObject.Namespace) == 0 && event.Namespace != metav1.NamespaceDefault && event.Namespace != metav1.NamespaceSystem { 160 allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) 161 } 162 if len(event.ReportingController) == 0 { 163 allErrs = append(allErrs, field.Required(field.NewPath(reportingControllerFieldName), "")) 164 } 165 allErrs = append(allErrs, ValidateQualifiedName(event.ReportingController, field.NewPath(reportingControllerFieldName))...) 166 if len(event.ReportingInstance) == 0 { 167 allErrs = append(allErrs, field.Required(field.NewPath("reportingInstance"), "")) 168 } 169 if len(event.ReportingInstance) > ReportingInstanceLengthLimit { 170 allErrs = append(allErrs, field.Invalid(field.NewPath("reportingInstance"), "", fmt.Sprintf("can have at most %v characters", ReportingInstanceLengthLimit))) 171 } 172 if len(event.Action) == 0 { 173 allErrs = append(allErrs, field.Required(field.NewPath("action"), "")) 174 } 175 if len(event.Action) > ActionLengthLimit { 176 allErrs = append(allErrs, field.Invalid(field.NewPath("action"), "", fmt.Sprintf("can have at most %v characters", ActionLengthLimit))) 177 } 178 if len(event.Reason) == 0 { 179 allErrs = append(allErrs, field.Required(field.NewPath("reason"), "")) 180 } 181 if len(event.Reason) > ReasonLengthLimit { 182 allErrs = append(allErrs, field.Invalid(field.NewPath("reason"), "", fmt.Sprintf("can have at most %v characters", ReasonLengthLimit))) 183 } 184 if len(event.Message) > NoteLengthLimit { 185 allErrs = append(allErrs, field.Invalid(field.NewPath("message"), "", fmt.Sprintf("can have at most %v characters", NoteLengthLimit))) 186 } 187 } 188 189 for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) { 190 allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg)) 191 } 192 return allErrs 193 }