mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-09 14:02:56 +09:00
Editing a VMDisk's storageClass or source after its DataVolume already
exists was silently ignored: dv.yaml reused the existing DataVolume.spec
verbatim (DataVolume.spec is immutable in CDI), so the edit rendered as a
no-op while the HelmRelease reported success and the CR diverged from the
cluster.
When a DataVolume already exists, fail with a clear message if the
requested storageClass or source differs from what the disk was created
with. Both compare the operator's input recorded in an annotation at
creation (vm-disk.cozystack.io/storage-class and .../source), written only
at creation and preserved verbatim afterwards — never overwritten with an
unapplied edit. Recording the input rather than reading the live object
matters on both axes: the image-clone PVC name carries a chart-version
prefix (vm-image- was renamed to vm-default-images-, commit bec35e3a) that
drifts across upgrades with no edit, and the live PVC storageClassName of an
adopted or foreign DataVolume may legitimately differ from the CR default
without being a change. The annotation key presence (not its value) marks a recorded class, so a disk
created with the cluster default "" is recorded and still caught if later
changed to a concrete class.
Disks with no annotation (created earlier, or out-of-band) are handled
conservatively so a working release never deadlocks: source is compared
against the stored spec.source only when that stored source is a shape the
chart itself produces, so an adopted DataVolume using spec.sourceRef or a
non-chart source (registry, snapshot, http+secretRef, ...) is left alone
rather than deadlocked. The image-clone exemption fires only when the stored
source is a cozy-public pvc AND the request is itself an image, so a change
away from an image source to another type is still caught. storageClass has
no safe live fallback, so an un-annotated disk is not guarded here (apiserver
CEL, #2657, is the eventual guard). An unrecognized source type
now fails explicitly. Storage growth is unaffected and still handled by
pvc-resize-hook.yaml.
Mark source as immutable in values.yaml so the generated schema carries
the same x-kubernetes-validations rule already present on storageClass,
and document the contract, the exemption, and the source-annotation schema
coupling in docs/storage-immutability.md and the chart README.
Add helm-unittest coverage for the immutability guard (rejection incl.
clearing storageClass to empty and changing a non-image disk to image,
no-op reuse with annotations preserved, legacy image-disk drift, annotation
non-poisoning, un-annotated storageClass skip, adopted default-class disk,
unknown source) and initial-creation source rendering.
Fixes #2985
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
99 lines
2.5 KiB
JSON
99 lines
2.5 KiB
JSON
{
|
|
"title": "Chart Values",
|
|
"type": "object",
|
|
"properties": {
|
|
"source": {
|
|
"description": "The source image location used to create a disk.",
|
|
"type": "object",
|
|
"default": {},
|
|
"properties": {
|
|
"disk": {
|
|
"description": "Clone an existing vm-disk.",
|
|
"type": "object",
|
|
"required": [
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"description": "Name of the vm-disk to clone.",
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"http": {
|
|
"description": "Download image from an HTTP source.",
|
|
"type": "object",
|
|
"required": [
|
|
"url"
|
|
],
|
|
"properties": {
|
|
"url": {
|
|
"description": "URL to download the image.",
|
|
"type": "string"
|
|
}
|
|
}
|
|
},
|
|
"image": {
|
|
"description": "Use image by name from default collection.",
|
|
"type": "object",
|
|
"required": [
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"description": "Name of the image to use.",
|
|
"type": "string",
|
|
"x-cozystack-options": {
|
|
"source": "image"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"upload": {
|
|
"description": "Upload local image.",
|
|
"type": "object"
|
|
}
|
|
},
|
|
"x-kubernetes-validations": [
|
|
{
|
|
"rule": "self == oldSelf",
|
|
"message": "source is immutable"
|
|
}
|
|
]
|
|
},
|
|
"optical": {
|
|
"description": "Defines if disk should be considered optical.",
|
|
"type": "boolean",
|
|
"default": false
|
|
},
|
|
"storage": {
|
|
"description": "The size of the disk allocated for the virtual machine.",
|
|
"default": "5Gi",
|
|
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
|
|
"anyOf": [
|
|
{
|
|
"type": "integer"
|
|
},
|
|
{
|
|
"type": "string"
|
|
}
|
|
],
|
|
"x-kubernetes-int-or-string": true
|
|
},
|
|
"storageClass": {
|
|
"description": "StorageClass used to store the data.",
|
|
"type": "string",
|
|
"default": "replicated",
|
|
"x-kubernetes-validations": [
|
|
{
|
|
"rule": "self == oldSelf",
|
|
"message": "storageClass is immutable"
|
|
}
|
|
],
|
|
"x-cozystack-options": {
|
|
"source": "storageclass"
|
|
}
|
|
}
|
|
}
|
|
}
|