|
62 | 62 | - [List Operations](#list-operations)
|
63 | 63 | - [Conditional Immutability](#conditional-immutability)
|
64 | 64 | - [Subresource-Specific Immutability](#subresource-specific-immutability)
|
| 65 | + - [Referencing Fields in Validation-Gen For Cross-Field Validation Rules](#referencing-fields-in-validation-gen-for-cross-field-validation-rules) |
| 66 | + - [Field Reference Strategies](#field-reference-strategies) |
| 67 | + - [Field Path Strategy](#field-path-strategy) |
| 68 | + - [Virtual Field Strategy](#virtual-field-strategy) |
| 69 | + - [Choosing Between Field Paths and Virtual Fields](#choosing-between-field-paths-and-virtual-fields) |
| 70 | + - [Tag Placement and Hoisting](#tag-placement-and-hoisting) |
| 71 | + - [Comprehensive Example:](#comprehensive-example) |
65 | 72 | - [Subresources](#subresources)
|
66 | 73 | - [Status-Type Subresources](#status-type-subresources)
|
67 | 74 | - [Scale-Type Subresources](#scale-type-subresources)
|
@@ -748,6 +755,28 @@ The below rules are currently implemented or are very similar to an existing val
|
748 | 755 | N/A
|
749 | 756 | </td>
|
750 | 757 | </tr>
|
| 758 | + <tr> |
| 759 | + <td style="background-color: null"> |
| 760 | + group membership (virtual field) |
| 761 | + </td> |
| 762 | + <td style="background-color: null"> |
| 763 | + `+k8s:memberOf(group: <groupname>)` |
| 764 | + </td> |
| 765 | + <td style="background-color: null"> |
| 766 | + N/A |
| 767 | + </td> |
| 768 | + </tr> |
| 769 | + <tr> |
| 770 | + <td style="background-color: null"> |
| 771 | + list map item reference (virtual field) |
| 772 | + </td> |
| 773 | + <td style="background-color: null"> |
| 774 | + `+k8s:listMapItem(pairs: [[key,value],...])` |
| 775 | + </td> |
| 776 | + <td style="background-color: null"> |
| 777 | + N/A |
| 778 | + </td> |
| 779 | + </tr> |
751 | 780 | </table>
|
752 | 781 |
|
753 | 782 | The below rules are not currently implemented in the [validation-gen prototype](https://github.com/jpbetz/kubernetes/tree/validation-gen) so the exact syntax is still WIP
|
@@ -1171,6 +1200,150 @@ type PodSpec struct {
|
1171 | 1200 | NodeName string `json:"nodeName,omitempty"`
|
1172 | 1201 | }
|
1173 | 1202 | ```
|
| 1203 | +### Referencing Fields in Validation-Gen For Cross-Field Validation Rules |
| 1204 | + |
| 1205 | +Cross-field validation refers to validation rules that depend on the values of multiple fields within a struct or across nested structs. Cross-field validations require a method for referencing additional fields from validation-gen IDL tags. |
| 1206 | + |
| 1207 | +#### Field Reference Strategies |
| 1208 | + |
| 1209 | +`validation-gen` supports two strategies for referencing fields in cross-field validations: |
| 1210 | + |
| 1211 | +1. **Field Paths** - Direct references using dot notation |
| 1212 | +1. **Virtual Fields** - Identifier-based references for relationships |
| 1213 | + |
| 1214 | +##### Field Path Strategy |
| 1215 | + |
| 1216 | +Field paths use JSON field names with dot notation to reference other fields. |
| 1217 | + |
| 1218 | +**When tags are placed on fields:** |
| 1219 | + |
| 1220 | +- Field paths are relative to the parent typedef (allowing sibling access) |
| 1221 | +- No special `self` or `parent` keywords allowed |
| 1222 | +- Example: `siblingField` or `siblingStruct.childField` |
| 1223 | + |
| 1224 | +```go |
| 1225 | +type Config struct { |
| 1226 | + MinValue int32 `json:"minValue"` |
| 1227 | + MaxValue int32 `json:"maxValue"` |
| 1228 | + |
| 1229 | + // +k8s:minimum(constraint: minValue) |
| 1230 | + // +k8s:maximum(constraint: maxValue) |
| 1231 | + Current int32 `json:"current"` |
| 1232 | +} |
| 1233 | +``` |
| 1234 | + |
| 1235 | +**When tags are placed on the common ancestor typedef:** |
| 1236 | + |
| 1237 | +- All references are to child fields |
| 1238 | + |
| 1239 | +```go |
| 1240 | +// NOTE: this is illustrative - minimum/maximum tags do not support "target" |
| 1241 | +// +k8s:minimum(target: current, constraint: minValue) |
| 1242 | +// +k8s:maximum(target: current, constraint: maxValue) |
| 1243 | +type Config struct { |
| 1244 | + MinValue int32 `json:"minValue"` |
| 1245 | + MaxValue int32 `json:"maxValue"` |
| 1246 | + Current int32 `json:"current"` |
| 1247 | +} |
| 1248 | +``` |
| 1249 | + |
| 1250 | +##### Virtual Field Strategy |
| 1251 | + |
| 1252 | +Virtual fields provide identifier-based references for cross-field relationships. They are scoped to a GVK (GroupVersionKind) namespace. |
| 1253 | + |
| 1254 | +**Virtual Field Reference Tags:** |
| 1255 | + |
| 1256 | +- `+k8s:memberOf(group: <groupname>)` - Adds field to a group for union/mutual exclusion validation |
| 1257 | +- `+k8s:listMapItem(pairs: [[key,value],...])` - References specific items by key value in listType=map lists where the key(s) must include all of the list maps's keys |
| 1258 | + |
| 1259 | +```go |
| 1260 | +type Config struct { |
| 1261 | + // +k8s:listType=map |
| 1262 | + // +k8s:listMapKey=type |
| 1263 | + // +k8s:union(union: terminalStatus) |
| 1264 | + // +k8s:listMapItem([["type","Succeeded"]])=+k8s:memberOf(group: terminalStatus) |
| 1265 | + // +k8s:listMapItem([["type","Failed"]])=+k8s:memberOf(group: terminalStatus) |
| 1266 | + Conditions []Condition `json:"conditions"` |
| 1267 | +} |
| 1268 | + |
| 1269 | +type Condition struct { |
| 1270 | + Type string `json:"type"` |
| 1271 | + Status string `json:"status"` |
| 1272 | +} |
| 1273 | +``` |
| 1274 | + |
| 1275 | +**Virtual Field Scope:** |
| 1276 | + |
| 1277 | +- Virtual fields are scoped to the given GVK. The logical namespace for virtual fields is their GVK. For example, the virtual field names in apps/v1/Deployment are internally namespaced as apps/v1/Deployment:minReplicas, apps/v1/Deployment:maxReplicas, etc. However, within the same GVK, these can be referenced using just their simple names (minReplicas, maxReplicas) without the namespace prefix. |
| 1278 | +- **Cross-GVK references are NOT supported** |
| 1279 | + - This mainly impacts any ObjectMeta name and generateName validation, which MUST be done using subfield and/or field-paths, not with virtual field references. |
| 1280 | + |
| 1281 | +#### Choosing Between Field Paths and Virtual Fields |
| 1282 | + |
| 1283 | +**Use Field Paths when:** |
| 1284 | + |
| 1285 | +- Making simple references to sibling or child fields |
| 1286 | +- All referenced fields are accessible via dot notation |
| 1287 | + |
| 1288 | +**Use Virtual Fields when:** |
| 1289 | + |
| 1290 | +- Implementing group-based rules (union validations, etc.) |
| 1291 | +- Referencing specific items in listType=map |
| 1292 | + |
| 1293 | +#### Tag Placement and Hoisting |
| 1294 | + |
| 1295 | +Cross-field validation tags can be placed on either tag location depending on what the tag supports: |
| 1296 | + |
| 1297 | +1. **On fields directly** - More intuitive but requires implicit hoisting to common ancestor |
| 1298 | +1. **On common ancestor typedef** - Explicit placement where validation actually occurs |
| 1299 | + |
| 1300 | +All cross-field validations are ultimately hoisted to execute at the common ancestor level to ensure proper access to all referenced fields during validation. |
| 1301 | + |
| 1302 | +#### Comprehensive Example: |
| 1303 | + |
| 1304 | +```go |
| 1305 | +type Config struct { |
| 1306 | + MinValue int `json:"minValue"` |
| 1307 | + MaxCpu int `json:"maxCpu"` |
| 1308 | + MaxThreshold int `json:"maxThreshold"` |
| 1309 | + |
| 1310 | + // +k8s:minimum(constraint: minValue) |
| 1311 | + // +k8s:maximum(constraint: limits.maxValue) |
| 1312 | + Current int `json:"current"` |
| 1313 | + |
| 1314 | + Limits LimitConfig `json:"limits"` |
| 1315 | + |
| 1316 | + Settings SettingsConfig `json:"settings"` |
| 1317 | + |
| 1318 | + // +k8s:eachVal=+k8s:maximum(constraint: maxThreshold) |
| 1319 | + Thresholds []int `json:"thresholds"` |
| 1320 | + |
| 1321 | + // +k8s:listType=map |
| 1322 | + // +k8s:listMapKey=type |
| 1323 | + // +k8s:union(union: terminalStatus) |
| 1324 | + // +k8s:listMapItem([["type","Succeeded"]])=+k8s:memberOf(group: terminalStatus) |
| 1325 | + // +k8s:listMapItem([["type","Failed"]])=+k8s:memberOf(group: terminalStatus) |
| 1326 | + Conditions []Condition `json:"conditions"` |
| 1327 | +} |
| 1328 | + |
| 1329 | +type LimitConfig struct { |
| 1330 | + MaxValue int `json:"maxValue"` |
| 1331 | +} |
| 1332 | + |
| 1333 | +type SettingsConfig struct { |
| 1334 | + Resources ResourceConfig `json:"resources"` |
| 1335 | +} |
| 1336 | + |
| 1337 | +type ResourceConfig struct { |
| 1338 | + Cpu int `json:"cpu"` |
| 1339 | +} |
| 1340 | + |
| 1341 | +type Condition struct { |
| 1342 | + Type string `json:"type"` |
| 1343 | + Status string `json:"status"` |
| 1344 | +} |
| 1345 | +``` |
| 1346 | + |
1174 | 1347 |
|
1175 | 1348 | ### Subresources
|
1176 | 1349 |
|
|
0 commit comments