|
55 | 55 | - [Difficulties with <code>+k8s:required</code> and <code>+k8s:default</code>](#difficulties-with-k8srequired-and-k8sdefault)
|
56 | 56 | - [Proposed Solutions](#proposed-solutions)
|
57 | 57 | - [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) |
58 | 65 | - [Subresources](#subresources)
|
59 | 66 | - [Status-Type Subresources](#status-type-subresources)
|
60 | 67 | - [Scale-Type Subresources](#scale-type-subresources)
|
@@ -708,6 +715,39 @@ The below rules are currently implemented or are very similar to an existing val
|
708 | 715 | N/A
|
709 | 716 | </td>
|
710 | 717 | </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> |
711 | 751 | </table>
|
712 | 752 |
|
713 | 753 | 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
|
948 | 988 |
|
949 | 989 | 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.
|
950 | 990 |
|
| 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 | + |
951 | 1175 | ### Subresources
|
952 | 1176 |
|
953 | 1177 | #### Status-Type Subresources
|
|
0 commit comments