Repository: kubernetes/kubernetes
File: pkg/registry/core/replicationcontroller/strategy.go (Go)

Code before:
	errs := append(validationErrorList, updateErrorList...)

	for key, value := range helper.NonConvertibleFields(oldRc.Annotations) {
		parts := strings.Split(key, "/")
		if len(parts) != 2 {
			continue
		}
		brokenField := parts[1]

		switch {
		case strings.Contains(brokenField, "selector"):
			if !apiequality.Semantic.DeepEqual(oldRc.Spec.Selector, newRc.Spec.Selector) {
				errs = append(errs, field.Invalid(field.NewPath("spec").Child("selector"), newRc.Spec.Selector, "cannot update non-convertible selector"))
			}
		default:
			errs = append(errs, &field.Error{Type: field.ErrorTypeNotFound, BadValue: value, Field: brokenField, Detail: "unknown non-convertible field"})
		}
	}

	// If DeclarativeValidation feature gate is enabled, run both validations
	if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
		// Determine if takeover is enabled
		takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)

		// Run both validations with panic recovery
		declarativeErrs := rest.ValidateDeclarativelyWithRecovery(ctx, nil, legacyscheme.Scheme, obj, takeover)
		declarativeErrs = append(
			declarativeErrs,
			rest.ValidateUpdateDeclarativelyWithRecovery(ctx, nil, legacyscheme.Scheme, obj, old, takeover)...)

		// Only apply declarative errors if takeover is enabled
		if takeover {
			errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
		}

		// Compare imperative and declarative errors and emit metric if there's a mismatch
		rest.CompareDeclarativeErrorsAndEmitMismatches(errs, declarativeErrs, takeover)
	}

	return errs
}

// WarningsOnUpdate returns warnings for the given update.
func (rcStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	var warnings []string
	oldRc := old.(*api.ReplicationController)
	newRc := obj.(*api.ReplicationController)
	if oldRc.Generation != newRc.Generation {
		warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), oldRc.Spec.Template, newRc.Spec.Template)
	}
	return warnings

Review comment (bug):
How does this respect ratcheting without the `old`.  As a pattern, checking the newObject to conform to current validation doesn't work well if the oldObject doesn't conform to current validation.  This is made even worse for spec/status validation being run on non-spec or non-status updates.

if a user updates field/Foo, an update failing on field/Bar that he didn't update is a problem.

Code after:
	validationErrorList := corevalidation.ValidateReplicationController(newRc, opts)
	updateErrorList := corevalidation.ValidateReplicationControllerUpdate(newRc, oldRc, opts)
	errs := append(validationErrorList, updateErrorList...)

	for key, value := range helper.NonConvertibleFields(oldRc.Annotations) {
		parts := strings.Split(key, "/")
		if len(parts) != 2 {
			continue
		}
		brokenField := parts[1]

		switch {
		case strings.Contains(brokenField, "selector"):
			if !apiequality.Semantic.DeepEqual(oldRc.Spec.Selector, newRc.Spec.Selector) {
				errs = append(errs, field.Invalid(field.NewPath("spec").Child("selector"), newRc.Spec.Selector, "cannot update non-convertible selector"))
			}
		default:
			errs = append(errs, &field.Error{Type: field.ErrorTypeNotFound, BadValue: value, Field: brokenField, Detail: "unknown non-convertible field"})
		}
	}

	// If DeclarativeValidation feature gate is enabled, also run declarative validation
	if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
		// Determine if takeover is enabled
		takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)

		// Run declarative update validation with panic recovery
		declarativeErrs := rest.ValidateUpdateDeclarativelyWithRecovery(ctx, nil, legacyscheme.Scheme, newRc, oldRc, takeover)

		// Compare imperative and declarative errors and emit metric if there's a mismatch
		rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover)

		// Only apply declarative errors if takeover is enabled
		if takeover {
			errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
		}
	}

	return errs
}

// WarningsOnUpdate returns warnings for the given update.
func (rcStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
	var warnings []string
	oldRc := old.(*api.ReplicationController)
	newRc := obj.(*api.ReplicationController)
	if oldRc.Generation != newRc.Generation {
		warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), oldRc.Spec.Template, newRc.Spec.Template)
	}
	return warnings
}