Skip to content

hypercore_disk, hypercore_nic, hypercore_vm_replication: PATCH payloads include read-only parent-reference fields rejected by HyperCore v2 API #109

@ddemlow

Description

@ddemlow

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

  1. internal/provider/hypercore_disk_resource.go — Update()

updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"capacity": ...,
"tieringPriorityFactor": ...,
}

  1. 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.

  1. internal/provider/hypercore_nic_resource.go — Update()

updatePayload := map[string]any{
"virDomainUUID": vmUUID, // ← rejected by v2 PATCH schema
"type": ...,
"vlan": ...,
"macAddress": ...,
}

  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions