Skip to content

✨ Link Host to VirtualMachine + validate Container image ID#239

Merged
fanshan merged 4 commits into
mainfrom
feat/host-virtual-machine
Jun 3, 2026
Merged

✨ Link Host to VirtualMachine + validate Container image ID#239
fanshan merged 4 commits into
mainfrom
feat/host-virtual-machine

Conversation

@fanshan
Copy link
Copy Markdown
Collaborator

@fanshan fanshan commented Jun 2, 2026

Host ↔ VirtualMachine relationship

  • Host model: add optional OneToOneField to virtualization.VirtualMachine (SET_NULL, related_name="docker_hosts"). One Host may be linked to at most one VirtualMachine; the link is optional on both sides.
  • Migration 1044_host_virtual_machine: AddField for the new FK.
  • HostTable: add linkified virtual_machine column (available but not in default columns).
  • HostSerializer (API): expose virtual_machine via VirtualMachineSerializer.
  • HostFilterSet / filtersets.py: virtual_machine_id filter.
  • HostView queryset: prefetch_related extended with virtual_machine.
  • host.html template: show the linked VM in the Host detail panel.
  • template_content.py + virtual_machine_host_table.html: inject a "Docker Hosts" table on the VirtualMachine detail page using NetBox's TemplateExtension mechanism.

Host add / edit form split

  • HostAddForm (new, NetBoxModelForm): used exclusively at creation time. Fields: name, endpoint, virtual_machine. Tags are suppressed via __init__self.fields.pop("tags", None) so that TagsMixin.__init__ still runs cleanly without a KeyError at line 143 of mixins.py.
  • HostForm (existing, NetBoxModelForm): used for editing. Gains virtual_machine field; keeps tags.
  • urls.py: hosts/add/ now routes to the new HostAddView.
  • HostAddView (new, ObjectEditView): uses HostAddForm, always runs the token-creation logic on POST (no dead not "pk" in url_kwargs guard needed).
  • HostEditView: dead alter_object override removed (edit URLs always carry a pk so the creation branch could never fire).

Container validation

  • Container.clean(): reject saving a Container whose Image has no ImageID (i.e. the image has not been pulled yet). Error targets the image field: "Image has no ID yet. Pull or refresh it first."

Tests

  • test_container_validation.py:
    • test_that_container_image_must_have_an_id: expects ValidationError when the image has no ImageID.
    • test_that_container_image_with_id_passes_validation: clean() succeeds when ImageID is set. Adds image1_pulled fixture (ImageID="sha256:abc123").
  • test_host_virtual_machine.py (new):
    • host without VM is valid (default None)
    • host with VM is saved and reloaded correctly
    • OneToOne constraint raises IntegrityError (wrapped in transaction.atomic() savepoint so the test transaction stays intact)
    • SET_NULL on VM deletion nullifies the host FK
    • unlinking sets FK to None without deleting either object
    • reverse accessor vm.docker_host returns the linked Host

## Host ↔ VirtualMachine relationship

- `Host` model: add optional `OneToOneField` to `virtualization.VirtualMachine`
  (`SET_NULL`, `related_name="docker_hosts"`). One Host may be linked to at
  most one VirtualMachine; the link is optional on both sides.
- Migration `1044_host_virtual_machine`: `AddField` for the new FK.
- `HostTable`: add linkified `virtual_machine` column (available but not in
  default columns).
- `HostSerializer` (API): expose `virtual_machine` via `VirtualMachineSerializer`.
- `HostFilterSet` / `filtersets.py`: `virtual_machine_id` filter.
- `HostView` queryset: `prefetch_related` extended with `virtual_machine`.
- `host.html` template: show the linked VM in the Host detail panel.
- `template_content.py` + `virtual_machine_host_table.html`: inject a
  "Docker Hosts" table on the VirtualMachine detail page using NetBox's
  `TemplateExtension` mechanism.

## Host add / edit form split

- `HostAddForm` (new, `NetBoxModelForm`): used exclusively at creation time.
  Fields: `name`, `endpoint`, `virtual_machine`. Tags are suppressed via
  `__init__` → `self.fields.pop("tags", None)` so that `TagsMixin.__init__`
  still runs cleanly without a `KeyError` at line 143 of `mixins.py`.
- `HostForm` (existing, `NetBoxModelForm`): used for editing. Gains
  `virtual_machine` field; keeps `tags`.
- `urls.py`: `hosts/add/` now routes to the new `HostAddView`.
- `HostAddView` (new, `ObjectEditView`): uses `HostAddForm`, always runs the
  token-creation logic on POST (no dead `not "pk" in url_kwargs` guard needed).
- `HostEditView`: dead `alter_object` override removed (edit URLs always carry
  a pk so the creation branch could never fire).

## Container validation

- `Container.clean()`: reject saving a Container whose Image has no `ImageID`
  (i.e. the image has not been pulled yet). Error targets the `image` field:
  "Image <name> has no ID yet. Pull or refresh it first."

## Tests

- `test_container_validation.py`:
  - `test_that_container_image_must_have_an_id`: expects `ValidationError`
    when the image has no `ImageID`.
  - `test_that_container_image_with_id_passes_validation`: `clean()` succeeds
    when `ImageID` is set. Adds `image1_pulled` fixture (`ImageID="sha256:abc123"`).
- `test_host_virtual_machine.py` (new):
  - host without VM is valid (default `None`)
  - host with VM is saved and reloaded correctly
  - OneToOne constraint raises `IntegrityError` (wrapped in `transaction.atomic()`
    savepoint so the test transaction stays intact)
  - `SET_NULL` on VM deletion nullifies the host FK
  - unlinking sets FK to `None` without deleting either object
  - reverse accessor `vm.docker_host` returns the linked Host

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fanshan fanshan requested a review from lvenier June 2, 2026 18:03
@fanshan fanshan self-assigned this Jun 2, 2026
fanshan and others added 3 commits June 2, 2026 20:04
@fanshan fanshan merged commit 6dfaa9d into main Jun 3, 2026
2 checks passed
@fanshan fanshan deleted the feat/host-virtual-machine branch June 3, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants