Skip to content

Commit c45b841

Browse files
committed
KEP-5073: Declarative Validation: Explain and update document with immutability validation information
1 parent 231224d commit c45b841

File tree

1 file changed

+224
-0
lines changed
  • keps/sig-api-machinery/5073-declarative-validation-with-validation-gen

1 file changed

+224
-0
lines changed

keps/sig-api-machinery/5073-declarative-validation-with-validation-gen/README.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
- [Difficulties with <code>+k8s:required</code> and <code>+k8s:default</code>](#difficulties-with-k8srequired-and-k8sdefault)
5656
- [Proposed Solutions](#proposed-solutions)
5757
- [Addressing the Problem with Valid Zero Values Using the Linter](#addressing-the-problem-with-valid-zero-values-using-the-linter)
58+
- [Immutability Validation](#immutability-validation)
59+
- [Core Concepts](#core-concepts)
60+
- [Immutability Patterns and Tags](#immutability-patterns-and-tags)
61+
- [Immutability and Lifecycle Patterns Demonstrated](#immutability-and-lifecycle-patterns-demonstrated)
62+
- [List Operations](#list-operations)
63+
- [Conditional Immutability](#conditional-immutability)
64+
- [Subresource-Specific Immutability](#subresource-specific-immutability)
5865
- [Subresources](#subresources)
5966
- [Status-Type Subresources](#status-type-subresources)
6067
- [Scale-Type Subresources](#scale-type-subresources)
@@ -708,6 +715,39 @@ The below rules are currently implemented or are very similar to an existing val
708715
N/A
709716
</td>
710717
</tr>
718+
<tr>
719+
<td style="background-color: null">
720+
immutable after set
721+
</td>
722+
<td style="background-color: null">
723+
`+k8s:immutable`
724+
</td>
725+
<td style="background-color: null">
726+
N/A
727+
</td>
728+
</tr>
729+
<tr>
730+
<td style="background-color: null">
731+
required once set
732+
</td>
733+
<td style="background-color: null">
734+
`+k8s:requiredOnceSet`
735+
</td>
736+
<td style="background-color: null">
737+
N/A
738+
</td>
739+
</tr>
740+
<tr>
741+
<td style="background-color: null">
742+
immutable(frozen) at creation
743+
</td>
744+
<td style="background-color: null">
745+
`+k8s:frozen`
746+
</td>
747+
<td style="background-color: null">
748+
N/A
749+
</td>
750+
</tr>
711751
</table>
712752

713753
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
@@ -948,6 +988,190 @@ The linter, as previously described, will enforce rules to address valid zero-va
948988

949989
The linter will flag any violations of these rules, ensuring consistent zero-value handling and preventing related errors. This automated enforcement is crucial for catching issues early in the development process.
950990

991+
### Immutability Validation
992+
993+
Kubernetes API fields have various immutability requirements based on their lifecycle patterns. This section defines the immutability validation tags and their semantics within the declarative validation framework.
994+
995+
#### Core Concepts
996+
997+
**Field States:**
998+
- **Unset**: Field has no value (nil, zero value, or absent)
999+
- **Set**: Field has a value
1000+
1001+
**State Transitions:**
1002+
- **Set**: Transition from unset->set
1003+
- **Clear**: Transition from set->unset
1004+
- **Modify**: Transition from one value to another value
1005+
1006+
**Lifecycle Scope:**
1007+
All immutability constraints are scoped to the parent object's lifecycle. When a parent object is replaced (e.g., a struct field set to a new instance), the immutability constraints apply to the new instance.
1008+
1009+
#### Immutability Patterns and Tags
1010+
1011+
The following tags implement different immutability patterns based on creation requirements and allowed transitions:
1012+
1013+
<table>
1014+
<tr>
1015+
<td><strong>Pattern</strong></td>
1016+
<td><strong>Creation Requirement</strong></td>
1017+
<td><strong>Allowed Transitions</strong></td>
1018+
<td><strong>Forbidden Transitions</strong></td>
1019+
<td><strong>Description</strong></td>
1020+
<td><strong>Use Case</strong></td>
1021+
</tr>
1022+
<tr>
1023+
<td>required</td>
1024+
<td>Must be present</td>
1025+
<td>modify</td>
1026+
<td>set, clear</td>
1027+
<td>Must be set at parent creation, can change, never removed</td>
1028+
<td>Field must always exist but can change</td>
1029+
</tr>
1030+
<tr>
1031+
<td>requiredOnceSet</td>
1032+
<td>Can be unset</td>
1033+
<td>set, modify</td>
1034+
<td>clear</td>
1035+
<td>Can be set later, can change, but once set cannot be removed</td>
1036+
<td>Optional field that once set must remain</td>
1037+
</tr>
1038+
<tr>
1039+
<td>immutable</td>
1040+
<td>Can be unset</td>
1041+
<td>set</td>
1042+
<td>modify, clear</td>
1043+
<td>Can be set once (now or later), then frozen</td>
1044+
<td>Field can be set once by user/controller</td>
1045+
</tr>
1046+
<tr>
1047+
<td>required+immutable</td>
1048+
<td>Must be present</td>
1049+
<td>none</td>
1050+
<td>set, modify, clear</td>
1051+
<td>Must be set at parent creation, value frozen forever</td>
1052+
<td>Identity field set at creation</td>
1053+
</tr>
1054+
<tr>
1055+
<td>frozen</td>
1056+
<td>Can be unset</td>
1057+
<td>none</td>
1058+
<td>set, modify, clear</td>
1059+
<td>Field state (set/unset) and value frozen at parent creation</td>
1060+
<td>Architectural decision at creation</td>
1061+
</tr>
1062+
</table>
1063+
1064+
#### Immutability and Lifecycle Patterns Demonstrated
1065+
1066+
```go
1067+
type DeploymentSpec struct {
1068+
// +k8s:required // Must exist, can be modified
1069+
// ✅ Create: replicas = 3
1070+
// ✅ Update: replicas = 5
1071+
// ❌ Update: replicas = nil
1072+
Replicas *int32
1073+
}
1074+
1075+
type PersistentVolumeStatus struct {
1076+
// +k8s:requiredOnceSet
1077+
// ✅ Create: phase = "" (volume created)
1078+
// ✅ Update: phase = "Available" (set once)
1079+
// ✅ Update: phase = "Bound" (modify)
1080+
// ❌ Update: phase = "" (cannot clear)
1081+
Phase PersistentVolumePhase
1082+
}
1083+
1084+
type PersistentVolumeClaimSpec struct {
1085+
// +k8s:immutable
1086+
// ✅ Create: volumeName = ""
1087+
// ✅ Update: volumeName = "pv-123" (set once by PV controller)
1088+
// ❌ Update: volumeName = "pv-456" (cannot change binding)
1089+
// ❌ Update: volumeName = "" (cannot unbind)
1090+
VolumeName string
1091+
}
1092+
1093+
type PersistentVolumeSpec struct {
1094+
// required+immutable: Must specify capacity at creation
1095+
// +k8s:required
1096+
// +k8s:immutable
1097+
// ✅ Create: capacity = {"storage": "10Gi"}
1098+
// ❌ Update: capacity = {"storage": "20Gi"}
1099+
Capacity ResourceList
1100+
}
1101+
1102+
type PodSpec struct {
1103+
// +k8s:frozen // State decided at creation (can be set or unset)
1104+
// Create with: hostNetwork = true -> Frozen true
1105+
// Create with: hostNetwork = false -> Frozen false
1106+
// Create without setting -> Frozen false (zero value)
1107+
HostNetwork bool
1108+
}
1109+
```
1110+
1111+
#### List Operations
1112+
1113+
Lists have two levels of constraints:
1114+
1115+
**List-Level Constraints:**
1116+
```go
1117+
type ExampleStatus struct {
1118+
// Array must exist (can be empty)
1119+
// +k8s:required
1120+
Conditions []Condition `json:"conditions"`
1121+
}
1122+
```
1123+
1124+
**Item-Level Constraints:**
1125+
For list items, clearing (nil-ing) a list is represented as unsetting all items. Item-level constraints can prevent list becoming nil:
1126+
1127+
```go
1128+
type CertificateSigningRequestStatus struct {
1129+
// List is optional, but certain items are permanent once added
1130+
// +k8s:optional
1131+
// +k8s:listType=map
1132+
// +k8s:listMapKey=type
1133+
// +k8s:listMapItem([["type","Approved"]])=+k8s:immutable
1134+
// +k8s:listMapItem([["type","Denied"]])=+k8s:immutable
1135+
Conditions []CertificateSigningRequestCondition `json:"conditions,omitempty"`
1136+
}
1137+
```
1138+
1139+
#### Conditional Immutability
1140+
1141+
Fields can have different immutability based on other field values:
1142+
1143+
```go
1144+
type Secret struct {
1145+
// Data becomes immutable(frozen) when immutable flag is true
1146+
// +k8s:EQ(field: "immutable", value: "true")=+k8s:frozen
1147+
Data map[string][]byte `json:"data,omitempty"`
1148+
1149+
// The immutable flag itself is immutable(frozen) once set to true
1150+
// +k8s:EQ(field: "immutable", value: "true")=+k8s:frozen
1151+
Immutable *bool `json:"immutable,omitempty"`
1152+
}
1153+
```
1154+
1155+
#### Subresource-Specific Immutability
1156+
1157+
Fields can have different immutability rules depending on the subresource:
1158+
1159+
```go
1160+
type Container struct {
1161+
// Immutable via main resource, mutable via /resize
1162+
// +k8s:required
1163+
// +k8s:ifNotSubresource("/resize")=+k8s:frozen
1164+
Resources ResourceRequirements `json:"resources,omitempty"`
1165+
}
1166+
1167+
type PodSpec struct {
1168+
// Can only be set via /binding subresource
1169+
// +k8s:ifNotSubresource("/binding")=+k8s:frozen
1170+
// +k8s:ifSubresource("/binding")=+k8s:immutable
1171+
NodeName string `json:"nodeName,omitempty"`
1172+
}
1173+
```
1174+
9511175
### Subresources
9521176

9531177
#### Status-Type Subresources

0 commit comments

Comments
 (0)