|
1 | 1 | package crdupgradesafety
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bytes" |
| 5 | + "cmp" |
4 | 6 | "errors"
|
5 | 7 | "fmt"
|
| 8 | + "reflect" |
6 | 9 | "slices"
|
7 | 10 |
|
8 | 11 | kappcus "carvel.dev/kapp/pkg/kapp/crdupgradesafety"
|
9 | 12 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
| 13 | + "k8s.io/apimachinery/pkg/util/sets" |
10 | 14 | versionhelper "k8s.io/apimachinery/pkg/version"
|
11 | 15 | )
|
12 | 16 |
|
@@ -70,3 +74,236 @@ func (c *ServedVersionValidator) Validate(old, new apiextensionsv1.CustomResourc
|
70 | 74 | func (c *ServedVersionValidator) Name() string {
|
71 | 75 | return "ServedVersionValidator"
|
72 | 76 | }
|
| 77 | + |
| 78 | +type resetFunc func(diff kappcus.FieldDiff) kappcus.FieldDiff |
| 79 | + |
| 80 | +func isHandled(diff kappcus.FieldDiff, reset resetFunc) bool { |
| 81 | + diff = reset(diff) |
| 82 | + return reflect.DeepEqual(diff.Old, diff.New) |
| 83 | +} |
| 84 | + |
| 85 | +func Enum(diff kappcus.FieldDiff) (bool, error) { |
| 86 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 87 | + diff.Old.Enum = []apiextensionsv1.JSON{} |
| 88 | + diff.New.Enum = []apiextensionsv1.JSON{} |
| 89 | + return diff |
| 90 | + } |
| 91 | + |
| 92 | + oldEnums := sets.New[string]() |
| 93 | + for _, json := range diff.Old.Enum { |
| 94 | + oldEnums.Insert(string(json.Raw)) |
| 95 | + } |
| 96 | + |
| 97 | + newEnums := sets.New[string]() |
| 98 | + for _, json := range diff.New.Enum { |
| 99 | + newEnums.Insert(string(json.Raw)) |
| 100 | + } |
| 101 | + diffEnums := oldEnums.Difference(newEnums) |
| 102 | + var err error |
| 103 | + |
| 104 | + switch { |
| 105 | + case oldEnums.Len() == 0 && newEnums.Len() > 0: |
| 106 | + err = fmt.Errorf("enum constraints %v added when there were no restrictions previously", newEnums.UnsortedList()) |
| 107 | + case diffEnums.Len() > 0: |
| 108 | + err = fmt.Errorf("enums %v removed from the set of previously allowed values", diffEnums.UnsortedList()) |
| 109 | + } |
| 110 | + |
| 111 | + return isHandled(diff, reset), err |
| 112 | +} |
| 113 | + |
| 114 | +func Required(diff kappcus.FieldDiff) (bool, error) { |
| 115 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 116 | + diff.Old.Required = []string{} |
| 117 | + diff.New.Required = []string{} |
| 118 | + return diff |
| 119 | + } |
| 120 | + |
| 121 | + oldRequired := sets.New(diff.Old.Required...) |
| 122 | + newRequired := sets.New(diff.New.Required...) |
| 123 | + diffRequired := newRequired.Difference(oldRequired) |
| 124 | + var err error |
| 125 | + |
| 126 | + if diffRequired.Len() > 0 { |
| 127 | + err = fmt.Errorf("new required fields %v added", diffRequired.UnsortedList()) |
| 128 | + } |
| 129 | + |
| 130 | + return isHandled(diff, reset), err |
| 131 | +} |
| 132 | + |
| 133 | +func maxVerification[T cmp.Ordered](older *T, newer *T) error { |
| 134 | + var err error |
| 135 | + switch { |
| 136 | + case older == nil && newer != nil: |
| 137 | + err = fmt.Errorf("constraint %v added when there were no restrictions previously", *newer) |
| 138 | + case older != nil && newer != nil && *newer < *older: |
| 139 | + err = fmt.Errorf("constraint decreased from %v to %v", *older, *newer) |
| 140 | + } |
| 141 | + return err |
| 142 | +} |
| 143 | + |
| 144 | +func Maximum(diff kappcus.FieldDiff) (bool, error) { |
| 145 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 146 | + diff.Old.Maximum = nil |
| 147 | + diff.New.Maximum = nil |
| 148 | + return diff |
| 149 | + } |
| 150 | + |
| 151 | + err := maxVerification(diff.Old.Maximum, diff.New.Maximum) |
| 152 | + if err != nil { |
| 153 | + err = fmt.Errorf("maximum: %s", err.Error()) |
| 154 | + } |
| 155 | + |
| 156 | + return isHandled(diff, reset), err |
| 157 | +} |
| 158 | + |
| 159 | +func MaxItems(diff kappcus.FieldDiff) (bool, error) { |
| 160 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 161 | + diff.Old.MaxItems = nil |
| 162 | + diff.New.MaxItems = nil |
| 163 | + return diff |
| 164 | + } |
| 165 | + |
| 166 | + err := maxVerification(diff.Old.MaxItems, diff.New.MaxItems) |
| 167 | + if err != nil { |
| 168 | + err = fmt.Errorf("maxItems: %s", err.Error()) |
| 169 | + } |
| 170 | + |
| 171 | + return isHandled(diff, reset), err |
| 172 | +} |
| 173 | + |
| 174 | +func MaxLength(diff kappcus.FieldDiff) (bool, error) { |
| 175 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 176 | + diff.Old.MaxLength = nil |
| 177 | + diff.New.MaxLength = nil |
| 178 | + return diff |
| 179 | + } |
| 180 | + |
| 181 | + err := maxVerification(diff.Old.MaxLength, diff.New.MaxLength) |
| 182 | + if err != nil { |
| 183 | + err = fmt.Errorf("maxLength: %s", err.Error()) |
| 184 | + } |
| 185 | + |
| 186 | + return isHandled(diff, reset), err |
| 187 | +} |
| 188 | + |
| 189 | +func MaxProperties(diff kappcus.FieldDiff) (bool, error) { |
| 190 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 191 | + diff.Old.MaxProperties = nil |
| 192 | + diff.New.MaxProperties = nil |
| 193 | + return diff |
| 194 | + } |
| 195 | + |
| 196 | + err := maxVerification(diff.Old.MaxProperties, diff.New.MaxProperties) |
| 197 | + if err != nil { |
| 198 | + err = fmt.Errorf("maxProperties: %s", err.Error()) |
| 199 | + } |
| 200 | + |
| 201 | + return isHandled(diff, reset), err |
| 202 | +} |
| 203 | + |
| 204 | +func minVerification[T cmp.Ordered](older *T, newer *T) error { |
| 205 | + var err error |
| 206 | + switch { |
| 207 | + case older == nil && newer != nil: |
| 208 | + err = fmt.Errorf("constraint %v added when there were no restrictions previously", *newer) |
| 209 | + case older != nil && newer != nil && *newer > *older: |
| 210 | + err = fmt.Errorf("constraint increased from %v to %v", *older, *newer) |
| 211 | + } |
| 212 | + return err |
| 213 | +} |
| 214 | + |
| 215 | +func Minimum(diff kappcus.FieldDiff) (bool, error) { |
| 216 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 217 | + diff.Old.Minimum = nil |
| 218 | + diff.New.Minimum = nil |
| 219 | + return diff |
| 220 | + } |
| 221 | + |
| 222 | + err := minVerification(diff.Old.Minimum, diff.New.Minimum) |
| 223 | + if err != nil { |
| 224 | + err = fmt.Errorf("minimum: %s", err.Error()) |
| 225 | + } |
| 226 | + |
| 227 | + return isHandled(diff, reset), err |
| 228 | +} |
| 229 | + |
| 230 | +func MinItems(diff kappcus.FieldDiff) (bool, error) { |
| 231 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 232 | + diff.Old.MinItems = nil |
| 233 | + diff.New.MinItems = nil |
| 234 | + return diff |
| 235 | + } |
| 236 | + |
| 237 | + err := minVerification(diff.Old.MinItems, diff.New.MinItems) |
| 238 | + if err != nil { |
| 239 | + err = fmt.Errorf("minItems: %s", err.Error()) |
| 240 | + } |
| 241 | + |
| 242 | + return isHandled(diff, reset), err |
| 243 | +} |
| 244 | + |
| 245 | +func MinLength(diff kappcus.FieldDiff) (bool, error) { |
| 246 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 247 | + diff.Old.MinLength = nil |
| 248 | + diff.New.MinLength = nil |
| 249 | + return diff |
| 250 | + } |
| 251 | + |
| 252 | + err := minVerification(diff.Old.MinLength, diff.New.MinLength) |
| 253 | + if err != nil { |
| 254 | + err = fmt.Errorf("minLength: %s", err.Error()) |
| 255 | + } |
| 256 | + |
| 257 | + return isHandled(diff, reset), err |
| 258 | +} |
| 259 | + |
| 260 | +func MinProperties(diff kappcus.FieldDiff) (bool, error) { |
| 261 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 262 | + diff.Old.MinProperties = nil |
| 263 | + diff.New.MinProperties = nil |
| 264 | + return diff |
| 265 | + } |
| 266 | + |
| 267 | + err := minVerification(diff.Old.MinProperties, diff.New.MinProperties) |
| 268 | + if err != nil { |
| 269 | + err = fmt.Errorf("minProperties: %s", err.Error()) |
| 270 | + } |
| 271 | + |
| 272 | + return isHandled(diff, reset), err |
| 273 | +} |
| 274 | + |
| 275 | +func Default(diff kappcus.FieldDiff) (bool, error) { |
| 276 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 277 | + diff.Old.Default = nil |
| 278 | + diff.New.Default = nil |
| 279 | + return diff |
| 280 | + } |
| 281 | + |
| 282 | + var err error |
| 283 | + |
| 284 | + switch { |
| 285 | + case diff.Old.Default == nil && diff.New.Default != nil: |
| 286 | + err = fmt.Errorf("default value %q added when there was no default previously", string(diff.New.Default.Raw)) |
| 287 | + case diff.Old.Default != nil && diff.New.Default == nil: |
| 288 | + err = fmt.Errorf("default value %q removed", string(diff.Old.Default.Raw)) |
| 289 | + case diff.Old.Default != nil && diff.New.Default != nil && !bytes.Equal(diff.Old.Default.Raw, diff.New.Default.Raw): |
| 290 | + err = fmt.Errorf("default value changed from %q to %q", string(diff.Old.Default.Raw), string(diff.New.Default.Raw)) |
| 291 | + } |
| 292 | + |
| 293 | + return isHandled(diff, reset), err |
| 294 | +} |
| 295 | + |
| 296 | +func Type(diff kappcus.FieldDiff) (bool, error) { |
| 297 | + reset := func(diff kappcus.FieldDiff) kappcus.FieldDiff { |
| 298 | + diff.Old.Type = "" |
| 299 | + diff.New.Type = "" |
| 300 | + return diff |
| 301 | + } |
| 302 | + |
| 303 | + var err error |
| 304 | + if diff.Old.Type != diff.New.Type { |
| 305 | + err = fmt.Errorf("type changed from %q to %q", diff.Old.Type, diff.New.Type) |
| 306 | + } |
| 307 | + |
| 308 | + return isHandled(diff, reset), err |
| 309 | +} |
0 commit comments