Summary
Three resources send fields on PATCH that HyperCore's v2 API (/rest/v2) rejects with HTTP 400. The v1 API silently accepted these fields; v2 uses additionalProperties:
false on all PATCH schemas, making them hard rejections. All three fields are parent-reference fields set at creation time that cannot be changed via PATCH.
This was discovered during a systematic compatibility audit of the Terraform provider against HyperCore v2/9.7.1 using authenticated OpenAPI spec comparison and provider
source analysis.
Affected resources and fields
┌──────────────────────────┬────────────────────────────────────┬─────────────────────┬───────────────────┬─────────────────────────┐
│ Resource │ Endpoint │ Field sent on PATCH │ Required on POST? │ Accepted on PATCH (v2)? │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_disk │ PATCH /VirDomainBlockDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_disk │ PATCH /VirDomainBlockDevice/{uuid} │ slot │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_nic │ PATCH /VirDomainNetDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_vm_replication │ PATCH /VirDomainReplication/{uuid} │ sourceDomainUUID │ ✅ yes │ ❌ 400 │
└──────────────────────────┴────────────────────────────────────┴─────────────────────┴───────────────────┴─────────────────────────┘
virDomainUUID (disk and NIC) and sourceDomainUUID (replication) are the UUID of the parent VM — a relationship established at creation that cannot be reassigned via
PATCH. slot specifies disk position at creation.
Root cause — exact locations
- internal/provider/hypercore_disk_resource.go — Update()
updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"capacity": ...,
"tieringPriorityFactor": ...,
}
- internal/utils/vm_disk.go — BuildDiskPayload()
Used via VMDisk.UpdateBlockDevice() in the clone/import disk-update path:
return map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": vd.Type,
"slot": vd.Slot, // ← rejected by v2 PATCH schema
"capacity": *vd.Size,
}
Both removed fields present. Affects VM clone and import workflows that trigger disk updates post-creation.
- internal/provider/hypercore_nic_resource.go — Update()
updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"vlan": ...,
"macAddress": ...,
}
- internal/utils/vm_replication.go — UpdateVMReplication()
payload := map[string]any{
"sourceDomainUUID": sourceVmUUID, // ← rejected by v2 PATCH schema
"connectionUUID": connectionUUID,
"label": label,
"enable": enable,
}
Resources confirmed safe (7/10)
┌────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Resource │ Why safe │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm │ PATCH sends only name, description, mem, numVCPU, tags, affinityStrategy, snapshotScheduleUUID — all retained in v2 │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_iso │ Update sends only {"name"} — size only appears in CREATE (POST), not Update │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_virtual_disk │ Update is intentionally a no-op — no PATCH issued │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_boot_order │ Uses POST, not PATCH │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_power_state │ Power state uses POST action, not PATCH │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_snapshot │ PATCH to /VirDomainSnapshotSchedule — unaffected endpoint │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_snapshot_schedule │ Same — unaffected endpoint │
└────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
No GPU, GPUProfile, MonitoringConfig, or ClusterSpec resources exist in the provider, so those absent-endpoint breaking changes do not apply.
Suggested fix
Remove the parent-reference fields from PATCH payloads. These fields are only meaningful at resource creation (POST) — they identify which VM a disk/NIC/replication
belongs to, which cannot be changed after creation.
hypercore_disk_resource.go Update():
updatePayload := map[string]any{
// Remove "virDomainUUID" — parent VM is immutable after creation
"type": ...,
"capacity": ...,
"tieringPriorityFactor": ...,
}
vm_disk.go BuildDiskPayload() (used in clone/import path):
return map[string]any{
// Remove "virDomainUUID" and "slot" — parent VM and position are set at creation
"type": vd.Type,
"capacity": *vd.Size,
}
hypercore_nic_resource.go Update():
updatePayload := map[string]any{
// Remove "virDomainUUID" — parent VM is immutable after creation
"type": ...,
"vlan": ...,
"macAddress": ...,
}
vm_replication.go UpdateVMReplication():
payload := map[string]any{
// Remove "sourceDomainUUID" — source VM is immutable after creation
"connectionUUID": connectionUUID,
"label": label,
"enable": enable,
}
Context
- HyperCore /rest/v1 is planned to be renamed/replaced by /rest/v2 as the public API
- v2 uses additionalProperties: false on all PATCH schemas — any field not explicitly in the schema causes a hard 400 rejection (not a silent no-op as in v1)
- The v2 API team is also aware of these missing fields and is planning to restore them in v2; this provider fix provides defence-in-depth so the provider works
correctly regardless of API version
- Since v1 silently ignores these fields, removing them from PATCH payloads is safe for both v1 and v2 — no version detection or conditional logic needed
- These three resources are the only provider → v2 incompatibilities found; all other resources (7/10) work correctly against v2/9.7.1 as-is
- The identical issue was found and reported separately in the scale_computing.hypercore Ansible collection, which hits the same three field categories across the same
three endpoint types
Summary
Three resources send fields on PATCH that HyperCore's v2 API (/rest/v2) rejects with HTTP 400. The v1 API silently accepted these fields; v2 uses additionalProperties:
false on all PATCH schemas, making them hard rejections. All three fields are parent-reference fields set at creation time that cannot be changed via PATCH.
This was discovered during a systematic compatibility audit of the Terraform provider against HyperCore v2/9.7.1 using authenticated OpenAPI spec comparison and provider
source analysis.
Affected resources and fields
┌──────────────────────────┬────────────────────────────────────┬─────────────────────┬───────────────────┬─────────────────────────┐
│ Resource │ Endpoint │ Field sent on PATCH │ Required on POST? │ Accepted on PATCH (v2)? │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_disk │ PATCH /VirDomainBlockDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_disk │ PATCH /VirDomainBlockDevice/{uuid} │ slot │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_nic │ PATCH /VirDomainNetDevice/{uuid} │ virDomainUUID │ ✅ yes │ ❌ 400 │
├──────────────────────────┼────────────────────────────────────┼─────────────────────┼───────────────────┼─────────────────────────┤
│ hypercore_vm_replication │ PATCH /VirDomainReplication/{uuid} │ sourceDomainUUID │ ✅ yes │ ❌ 400 │
└──────────────────────────┴────────────────────────────────────┴─────────────────────┴───────────────────┴─────────────────────────┘
virDomainUUID (disk and NIC) and sourceDomainUUID (replication) are the UUID of the parent VM — a relationship established at creation that cannot be reassigned via
PATCH. slot specifies disk position at creation.
Root cause — exact locations
updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"capacity": ...,
"tieringPriorityFactor": ...,
}
Used via VMDisk.UpdateBlockDevice() in the clone/import disk-update path:
return map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": vd.Type,
"slot": vd.Slot, // ← rejected by v2 PATCH schema
"capacity": *vd.Size,
}
Both removed fields present. Affects VM clone and import workflows that trigger disk updates post-creation.
updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"vlan": ...,
"macAddress": ...,
}
payload := map[string]any{
"sourceDomainUUID": sourceVmUUID, // ← rejected by v2 PATCH schema
"connectionUUID": connectionUUID,
"label": label,
"enable": enable,
}
Resources confirmed safe (7/10)
┌────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Resource │ Why safe │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm │ PATCH sends only name, description, mem, numVCPU, tags, affinityStrategy, snapshotScheduleUUID — all retained in v2 │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_iso │ Update sends only {"name"} — size only appears in CREATE (POST), not Update │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_virtual_disk │ Update is intentionally a no-op — no PATCH issued │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_boot_order │ Uses POST, not PATCH │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_power_state │ Power state uses POST action, not PATCH │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_snapshot │ PATCH to /VirDomainSnapshotSchedule — unaffected endpoint │
├────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ hypercore_vm_snapshot_schedule │ Same — unaffected endpoint │
└────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
No GPU, GPUProfile, MonitoringConfig, or ClusterSpec resources exist in the provider, so those absent-endpoint breaking changes do not apply.
Suggested fix
Remove the parent-reference fields from PATCH payloads. These fields are only meaningful at resource creation (POST) — they identify which VM a disk/NIC/replication
belongs to, which cannot be changed after creation.
hypercore_disk_resource.go Update():
updatePayload := map[string]any{
// Remove "virDomainUUID" — parent VM is immutable after creation
"type": ...,
"capacity": ...,
"tieringPriorityFactor": ...,
}
vm_disk.go BuildDiskPayload() (used in clone/import path):
return map[string]any{
// Remove "virDomainUUID" and "slot" — parent VM and position are set at creation
"type": vd.Type,
"capacity": *vd.Size,
}
hypercore_nic_resource.go Update():
updatePayload := map[string]any{
// Remove "virDomainUUID" — parent VM is immutable after creation
"type": ...,
"vlan": ...,
"macAddress": ...,
}
vm_replication.go UpdateVMReplication():
payload := map[string]any{
// Remove "sourceDomainUUID" — source VM is immutable after creation
"connectionUUID": connectionUUID,
"label": label,
"enable": enable,
}
Context
correctly regardless of API version
three endpoint types