@@ -203,7 +203,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation
203203 }
204204
205205 r .warnLegacyPresets (app )
206- r .warnRemovedKubernetesFields (ctx , app )
206+ r .warnRemovedFields (ctx , app )
207207
208208 // Run the genericapiserver-supplied validating admission chain
209209 // (validating webhooks + ValidatingAdmissionPolicies) before
@@ -551,7 +551,7 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje
551551 }
552552
553553 r .warnLegacyPresets (app )
554- r .warnRemovedKubernetesFields (ctx , app )
554+ r .warnRemovedFields (ctx , app )
555555
556556 // Convert Application to HelmRelease
557557 helmRelease , err := r .ConvertApplicationToHelmRelease (app )
@@ -1946,32 +1946,78 @@ func (r *REST) warnLegacyPresets(app *appsv1alpha1.Application) {
19461946 }
19471947}
19481948
1949- // removedKubernetesFields are Kubernetes CR value keys that Phase 2 moved to the
1950- // separate KubernetesNodes resource. They are still accepted and stored -- an
1951- // upgraded cluster keeps them in its values until re-edited, and the adoption
1952- // migration deliberately leaves them in place rather than rewriting parent
1953- // values mid-upgrade -- but they no longer render anything on the Kubernetes CR.
1954- var removedKubernetesFields = []string {"nodeGroups" , "nodeHealthCheck" , "maxNodeProvisionTime" }
1955-
1956- // warnRemovedKubernetesFields emits a client-facing admission warning for each
1957- // Phase 2-removed field still present in a Kubernetes CR's values, so an
1958- // operator editing one (e.g. bumping spec.nodeGroups.<x>.minReplicas) is told it
1959- // has no effect instead of silently getting nothing. Non-blocking: the field is
1960- // preserved as-is; worker pools are managed as KubernetesNodes resources.
1961- func (r * REST ) warnRemovedKubernetesFields (ctx context.Context , app * appsv1alpha1.Application ) {
1962- if r .kindName != kubernetesKind || app == nil || app .Spec == nil || len (app .Spec .Raw ) == 0 {
1949+ // removedField is a values key whose chart has stopped reading it. Such a key
1950+ // is still accepted and stored -- an upgraded release keeps it until re-edited,
1951+ // and the migrations deliberately leave it in place rather than rewriting
1952+ // values mid-upgrade -- so the write path says so rather than letting the field
1953+ // look effective.
1954+ type removedField struct {
1955+ // path is the values key, with "." between the segments of a nested one.
1956+ path string
1957+ // replacement says what happens instead, in the second half of the warning.
1958+ replacement string
1959+ }
1960+
1961+ var removedFieldsByKind = map [string ][]removedField {
1962+ kubernetesKind : {
1963+ {path : "nodeGroups" , replacement : "worker pools are managed as separate KubernetesNodes resources (see the kubernetes-nodes chart)" },
1964+ {path : "nodeHealthCheck" , replacement : "worker pools are managed as separate KubernetesNodes resources (see the kubernetes-nodes chart)" },
1965+ {path : "maxNodeProvisionTime" , replacement : "worker pools are managed as separate KubernetesNodes resources (see the kubernetes-nodes chart)" },
1966+ {path : "images" , replacement : "the images come from the chart, and an air-gapped install moves them with the platform-wide registry" },
1967+ },
1968+ "KubernetesNodes" : {
1969+ {path : "images" , replacement : "the images come from the chart, and an air-gapped install moves them with the platform-wide registry" },
1970+ },
1971+ "OpenSearch" : {
1972+ {path : "images" , replacement : "the image follows spec.version" },
1973+ },
1974+ "FoundationDB" : {
1975+ {path : "cluster.version" , replacement : "the version is selected with spec.version" },
1976+ },
1977+ }
1978+
1979+ // warnRemovedFields emits a client-facing admission warning for each removed
1980+ // field still present in the values, so an operator editing one (e.g. bumping
1981+ // spec.nodeGroups.<x>.minReplicas) is told it has no effect instead of silently
1982+ // getting nothing. Non-blocking: the field is preserved as-is.
1983+ func (r * REST ) warnRemovedFields (ctx context.Context , app * appsv1alpha1.Application ) {
1984+ fields , ok := removedFieldsByKind [r .kindName ]
1985+ if ! ok || app == nil || app .Spec == nil || len (app .Spec .Raw ) == 0 {
19631986 return
19641987 }
19651988 var values map [string ]any
19661989 if err := json .Unmarshal (app .Spec .Raw , & values ); err != nil {
19671990 return
19681991 }
1969- for _ , key := range removedKubernetesFields {
1970- if _ , present := values [key ]; present {
1971- warning .AddWarning (ctx , "" , fmt .Sprintf (
1972- "spec.%s is ignored on the Kubernetes resource since Phase 2: worker pools are managed as separate KubernetesNodes resources (see the kubernetes-nodes chart). The field is stored but has no effect." , key ))
1992+ for _ , f := range fields {
1993+ if ! hasValuesPath (values , strings .Split (f .path , "." )) {
1994+ continue
1995+ }
1996+ warning .AddWarning (ctx , "" , fmt .Sprintf (
1997+ "spec.%s is ignored on the %s resource: %s. The field is stored but has no effect." ,
1998+ f .path , r .kindName , f .replacement ))
1999+ }
2000+ }
2001+
2002+ // hasValuesPath reports whether the values carry every segment of the path,
2003+ // walking only objects: a segment under a scalar is not present.
2004+ func hasValuesPath (values map [string ]any , path []string ) bool {
2005+ current := values
2006+ for i , segment := range path {
2007+ value , present := current [segment ]
2008+ if ! present {
2009+ return false
2010+ }
2011+ if i == len (path )- 1 {
2012+ return true
2013+ }
2014+ next , ok := value .(map [string ]any )
2015+ if ! ok {
2016+ return false
19732017 }
2018+ current = next
19742019 }
2020+ return false
19752021}
19762022
19772023// errNotAcceptable indicates that the resource does not support conversion to Table
0 commit comments