Add CRD validation.
Formatting rules are checked via deserialization from JSON. Logical rules are checked explicitly. Enums are checked in go code to avoid problems with old kubectl which don't support enums nor arrays in CRD validation.
This commit is contained in:
parent
dd92706b69
commit
c9d06fe93f
|
|
@ -125,11 +125,73 @@ func (s *AdmissionServer) getPatchesForPodResourceRequest(raw []byte, namespace
|
|||
return patches, nil
|
||||
}
|
||||
|
||||
func getPatchesForVPADefaults(raw []byte) ([]patchRecord, error) {
|
||||
func parseVPA(raw []byte) (*vpa_types.VerticalPodAutoscaler, error) {
|
||||
vpa := vpa_types.VerticalPodAutoscaler{}
|
||||
if err := json.Unmarshal(raw, &vpa); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vpa, nil
|
||||
}
|
||||
|
||||
var (
|
||||
possibleUpdateModes = map[vpa_types.UpdateMode]interface{}{
|
||||
vpa_types.UpdateModeOff: struct{}{},
|
||||
vpa_types.UpdateModeInitial: struct{}{},
|
||||
vpa_types.UpdateModeRecreate: struct{}{},
|
||||
vpa_types.UpdateModeAuto: struct{}{},
|
||||
}
|
||||
|
||||
possibleScalingModes = map[vpa_types.ContainerScalingMode]interface{}{
|
||||
vpa_types.ContainerScalingModeAuto: struct{}{},
|
||||
vpa_types.ContainerScalingModeOff: struct{}{},
|
||||
}
|
||||
)
|
||||
|
||||
func validateVPA(vpa *vpa_types.VerticalPodAutoscaler) error {
|
||||
if vpa.Spec.UpdatePolicy != nil {
|
||||
mode := vpa.Spec.UpdatePolicy.UpdateMode
|
||||
if mode == nil {
|
||||
return fmt.Errorf("UpdateMode is required if UpdatePolicy is used")
|
||||
}
|
||||
if _, found := possibleUpdateModes[*mode]; !found {
|
||||
return fmt.Errorf("Unexpected UpdateMode value %s", *mode)
|
||||
}
|
||||
}
|
||||
|
||||
if vpa.Spec.ResourcePolicy != nil {
|
||||
for _, policy := range vpa.Spec.ResourcePolicy.ContainerPolicies {
|
||||
if policy.ContainerName == "" {
|
||||
return fmt.Errorf("ContainerPolicies.ContainerName is required")
|
||||
}
|
||||
mode := policy.Mode
|
||||
if mode != nil {
|
||||
if _, found := possibleScalingModes[*mode]; !found {
|
||||
return fmt.Errorf("Unexpected Mode value %s", *mode)
|
||||
}
|
||||
}
|
||||
for resource, min := range policy.MinAllowed {
|
||||
max, found := policy.MaxAllowed[resource]
|
||||
if found && max.Cmp(min) < 0 {
|
||||
return fmt.Errorf("Max resource for %v is lower than min", resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPatchesForVPADefaults(raw []byte) ([]patchRecord, error) {
|
||||
vpa, err := parseVPA(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = validateVPA(vpa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Processing vpa: %v", vpa)
|
||||
patches := []patchRecord{}
|
||||
if vpa.Spec.UpdatePolicy == nil {
|
||||
|
|
@ -144,10 +206,14 @@ func getPatchesForVPADefaults(raw []byte) ([]patchRecord, error) {
|
|||
}
|
||||
|
||||
func (s *AdmissionServer) admit(data []byte) (*v1beta1.AdmissionResponse, metrics_admission.AdmissionStatus, metrics_admission.AdmissionResource) {
|
||||
// we don't block the admission by default, even on unparsable JSON
|
||||
response := v1beta1.AdmissionResponse{}
|
||||
response.Allowed = true
|
||||
|
||||
ar := v1beta1.AdmissionReview{}
|
||||
if err := json.Unmarshal(data, &ar); err != nil {
|
||||
glog.Error(err)
|
||||
return nil, metrics_admission.Error, metrics_admission.Unknown
|
||||
return &response, metrics_admission.Error, metrics_admission.Unknown
|
||||
}
|
||||
// The externalAdmissionHookConfiguration registered via selfRegistration
|
||||
// asks the kube-apiserver only to send admission requests regarding pods & VPA objects.
|
||||
|
|
@ -164,21 +230,28 @@ func (s *AdmissionServer) admit(data []byte) (*v1beta1.AdmissionResponse, metric
|
|||
case vpaResource:
|
||||
patches, err = getPatchesForVPADefaults(ar.Request.Object.Raw)
|
||||
resource = metrics_admission.Vpa
|
||||
// we don't let in problematic VPA objects - late validation
|
||||
if err != nil {
|
||||
status := metav1.Status{}
|
||||
status.Status = "Failure"
|
||||
status.Message = err.Error()
|
||||
response.Result = &status
|
||||
response.Allowed = false
|
||||
}
|
||||
default:
|
||||
patches, err = nil, fmt.Errorf("expected the resource to be %v or %v", podResource, vpaResource)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, metrics_admission.Error, resource
|
||||
return &response, metrics_admission.Error, resource
|
||||
}
|
||||
response := v1beta1.AdmissionResponse{}
|
||||
response.Allowed = true
|
||||
|
||||
if len(patches) > 0 {
|
||||
patch, err := json.Marshal(patches)
|
||||
if err != nil {
|
||||
glog.Errorf("Cannot marshal the patch %v: %v", patches, err)
|
||||
return nil, metrics_admission.Error, resource
|
||||
return &response, metrics_admission.Error, resource
|
||||
}
|
||||
patchType := v1beta1.PatchTypeJSONPatch
|
||||
response.PatchType = &patchType
|
||||
|
|
|
|||
Loading…
Reference in New Issue