Skip to content

Netbox Update Interfaces Description Task¤

task api name: update_interfaces_description

Updates the description field of interfaces, console ports, and console server ports for one or more devices in NetBox. Supports two mutually usable modes: template mode (description_template) renders descriptions dynamically using Jinja2 with interface and connection context where available, and static mode (descriptions) applies a fixed mapping of interface names to description strings.

How it Works¤

  1. Client submits update_interfaces_description request to NetBox worker
  2. Worker resolves the target NetBox instance and optionally the branch
  3. If description_template is provided, the worker fetches interface connections via get_connections and NetBox interface objects for the selected devices
  4. For each selected interface, the Jinja2 template is rendered with interface context and, when present, connection context (remote device, cable attributes, etc.)
  5. If descriptions dict is provided, the worker iterates over the given device list and applies the fixed description values directly
  6. In dry-run mode — the before (-) / after (+) diff is returned without writing to NetBox
  7. Otherwise, the new description is saved to NetBox

Prerequisites¤

  • The devices must already exist in NetBox.
  • Only interfaces, console ports, console server ports and power outlet ports are supported as port types.

Branching Support¤

update_interfaces_description is branch-aware. Pass branch=<name> to write all changes into a NetBox Branching Plugin branch instead of main.

Dry Run Mode¤

dry_run=True returns the before/after description diff without any NetBox writes:

{
    "<device>": {
        "<interface>": {
            "-": "<current description>",
            "+": "<new description>",
        },
        ...
    },
    ...
}

Template Mode¤

When description_template is provided, the Jinja2 template is rendered once per selected interface. Connected interfaces receive the full connection context. Virtual, LAG, or disconnected interfaces that have no connection data are still rendered with device, interface, and empty remote/cable fields. The template can be an inline string or a remote NorFab file reference (nf://path/to/template.txt).

Jinja2 context variables available in the template:

  • device — pynetbox dcim.device object
  • interface — pynetbox interface/console port/console server port object
  • remote_device — string
  • remote_interface — string
  • termination_type — string
  • cable — dictionary of directly attached cable attributes:
    • type
    • status
    • tenant — dictionary {name: tenant_name}
    • label
    • tags — list of {name: tag_name} dictionaries
    • custom_fields — dictionary with custom fields data
    • peer_termination_type
    • peer_device
    • peer_interface

Example template:

{{ remote_device }}:{{ remote_interface }}

Static Mode¤

When descriptions dict is provided, the same mapping is applied to all devices in the devices list. Only interfaces that exist in NetBox are updated — missing interfaces are silently skipped.

descriptions = {
    "Ethernet1": "uplink to spine-1",
    "Ethernet2": "uplink to spine-2",
}

Examples¤

Update interface descriptions using a Jinja2 template:

nf#netbox update interfaces description devices ceos-leaf-1 description-template "{{ remote_device }}:{{ remote_interface }}"

Update only selected interfaces:

nf#netbox update interfaces description devices ceos-leaf-1 interfaces Ethernet1 Ethernet2 description-template "{{ remote_device }}:{{ remote_interface }}"

Filter interfaces by regex pattern:

nf#netbox update interfaces description devices ceos-leaf-1 description-template "{{ remote_device }}:{{ remote_interface }}" interface-regex "Ethernet.*"

Dry run - preview changes without writing:

nf#netbox update interfaces description devices ceos-leaf-1 description-template "{{ remote_device }}:{{ remote_interface }}" dry-run

Apply static descriptions to matching interface names:

nf#netbox update interfaces description devices ceos-leaf-1 descriptions {"Ethernet1":"uplink to spine-1","Ethernet2":"uplink to spine-2"}

Update descriptions in a NetBox branch:

nf#netbox update interfaces description devices ceos-leaf-1 description-template "{{ remote_device }}:{{ remote_interface }}" branch my-branch

Context manager:

from norfab.core.nfapi import NorFab

with NorFab(inventory="./inventory.yaml") as nf:
    client = nf.make_client()

    result = client.run_job(
        "netbox",
        "update_interfaces_description",
        workers="any",
        kwargs={
            "devices": ["ceos-leaf-1", "ceos-leaf-2"],
            "description_template": "{{ remote_device }}:{{ remote_interface }}",
        },
    )

Direct lifecycle:

from norfab.core.nfapi import NorFab

nf = NorFab(inventory="./inventory.yaml")
try:
    nf.start()
    client = nf.make_client()

    preview = client.run_job(
        "netbox",
        "update_interfaces_description",
        workers="any",
        kwargs={
            "devices": ["ceos-leaf-1"],
            "interfaces": ["Ethernet1", "Ethernet2"],
            "description_template": "{{ remote_device }}:{{ remote_interface }}",
            "dry_run": True,
        },
    )

    static_result = client.run_job(
        "netbox",
        "update_interfaces_description",
        workers="any",
        kwargs={
            "devices": ["ceos-leaf-1", "ceos-leaf-2"],
            "descriptions": {
                "Ethernet1": "uplink to spine-1",
                "Ethernet2": "uplink to spine-2",
            },
            "branch": "my-branch",
        },
    )

    template_result = client.run_job(
        "netbox",
        "update_interfaces_description",
        workers="any",
        kwargs={
            "devices": ["ceos-leaf-1"],
            "description_template": "nf://templates/intf_desc.j2",
            "interface_regex": "Ethernet.*",
        },
    )
finally:
    nf.destroy()

NORFAB Netbox Update Interfaces Description Command Shell Reference¤

NorFab shell supports these command options for the Netbox update_interfaces_description task:

nf# man tree netbox.update.interfaces.description
root
└── netbox:    Netbox service
    └── update:    Update Netbox objects
        └── interfaces:    Update interfaces
            └── description:    Updates the description of interfaces for specified devices in NetBox
                ├── timeout:    Job timeout
                ├── workers:    Filter worker to target, default 'any'
                ├── verbose-result:    Control output details, default 'False'
                ├── progress:    Display progress events, default 'True'
                ├── instance:    Netbox instance name to target
                ├── branch:    Branching plugin branch name to use
                ├── dry-run:    Return diff without writing to NetBox
                ├── devices:    Device names to update interfaces for
                ├── description-template:    Jinja2 template to render descriptions
                ├── descriptions:    Dict keyed by interface name with description string values
                ├── interfaces:    Specific interface names to update
                └── interface-regex:    Regex pattern to match interfaces and ports
nf#

Python API Reference¤

Updates the description of interfaces for specified devices in NetBox.

This method retrieves interface connections for the given devices, renders new descriptions using a Jinja2 template, and updates the interface descriptions in NetBox accordingly.

Only interfaces, console ports and console server ports supported.

Jinja2 environment receives these context variables for description template rendering:

  • device - pynetbox dcim.device object
  • interface - pynetbox object - dcim/interface, dcip.consoleport, dcim.consoleserverport - depending on what kind of interface is that.
  • remote_device - string
  • remote_interface - string
  • termination_type - string
  • cable - dictionary of directly attached cable attributes:
    • type
    • status
    • tenant - dictionary of {name: tenant_name}
    • label
    • tags - list of {name: tag_name} dictionaries
    • custom_fields - dictionary with custom fields data
    • peer_termination_type
    • peer_device
    • peer_interface

Parameters:

Name Type Description Default
job Job

The job context for logging and event handling.

required
devices list

List of device names to update interfaces for.

required
description_template str

Jinja2 template string for the interface description. Can reference remote template using nf://path/to/template.txt.

None
descriptions dict

Dictionary keyed by interface names with values being interface description strings

None
interfaces Union[None, list]

Specific interfaces to update.

None
interface_regex Union[None, str]

Regex pattern to filter interfaces.

None
instance Union[None, str]

NetBox instance identifier.

None
dry_run bool

If True, performs a dry run without saving changes.

False
timeout int

Timeout for NetBox API requests.

600
branch str

Branch name for NetBox instance.

None

Returns:

Name Type Description
Result Result

An object containing the outcome of the update operation, including before and after descriptions.

Source code in norfab\workers\netbox_worker\interfaces_tasks.py
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
@Task(
    fastapi={"methods": ["PATCH"], "schema": NetboxFastApiArgs.model_json_schema()},
    input=UpdateInterfacesDescriptionInput,
    output=UpdateInterfacesDescriptionResult,
    mcp={
        "annotations": {
            "title": "Update Interface Descriptions",
            "readOnlyHint": False,
            "destructiveHint": True,
            "idempotentHint": True,
            "openWorldHint": True,
        }
    },
)
def update_interfaces_description(
    self,
    job: Job,
    devices: list,
    description_template: str = None,
    descriptions: dict = None,
    interfaces: Union[None, list] = None,
    interface_regex: Union[None, str] = None,
    instance: Union[None, str] = None,
    dry_run: bool = False,
    timeout: int = 600,
    branch: str = None,
) -> Result:
    """
    Updates the description of interfaces for specified devices in NetBox.

    This method retrieves interface connections for the given devices, renders
    new descriptions using a Jinja2 template, and updates the interface descriptions
    in NetBox accordingly.

    Only interfaces, console ports and console server ports supported.

    Jinja2 environment receives these context variables for description template rendering:

    - device - pynetbox `dcim.device` object
    - interface - pynetbox object - `dcim/interface`, `dcip.consoleport`,
        `dcim.consoleserverport` - depending on what kind of interface is that.
    - remote_device - string
    - remote_interface - string
    - termination_type - string
    - cable - dictionary of directly attached cable attributes:
        - type
        - status
        - tenant - dictionary of `{name: tenant_name}`
        - label
        - tags - list of `{name: tag_name}` dictionaries
        - custom_fields - dictionary with custom fields data
        - peer_termination_type
        - peer_device
        - peer_interface

    Args:
        job (Job): The job context for logging and event handling.
        devices (list): List of device names to update interfaces for.
        description_template (str): Jinja2 template string for the interface description.
            Can reference remote template using `nf://path/to/template.txt`.
        descriptions (dict): Dictionary keyed by interface names with values being interface
            description strings
        interfaces (Union[None, list], optional): Specific interfaces to update.
        interface_regex (Union[None, str], optional): Regex pattern to filter interfaces.
        instance (Union[None, str], optional): NetBox instance identifier.
        dry_run (bool, optional): If True, performs a dry run without saving changes.
        timeout (int, optional): Timeout for NetBox API requests.
        branch (str, optional): Branch name for NetBox instance.

    Returns:
        Result: An object containing the outcome of the update operation, including
            before and after descriptions.
    """
    result = {}
    instance = instance or self.default_instance
    ret = Result(
        task=f"{self.name}:update_interfaces_description",
        result=result,
        resources=[instance],
        dry_run=dry_run,
    )
    nb = self._get_pynetbox(instance, branch=branch)
    update_action = "would update" if dry_run else "updating"
    apply_action = "would apply" if dry_run else "applying"
    log.info(
        f"{self.name} - Update interfaces description: "
        f"{update_action.capitalize()} descriptions for "
        f"{len(devices)} device(s) in '{instance}'"
    )

    job.event(
        f"{update_action} interface descriptions for "
        f"{len(devices)} device(s), dry_run={dry_run}"
    )

    if description_template:
        job.event("rendering interface descriptions from interface data")
        # get list of all interface connections
        nb_connections = self.get_connections(
            job=job,
            devices=devices,
            interfaces=interfaces,
            interface_regex=interface_regex,
            instance=instance,
        )
        if nb_connections.errors:
            ret.errors.extend(nb_connections.errors)
            ret.failed = nb_connections.failed
            return ret

        # Fetch NetBox interfaces as well so virtual/disconnected interfaces
        # absent from get_connections still receive template-rendered descriptions.
        interface_filter = {"device": devices}
        if interfaces:
            interface_filter["name"] = interfaces
        if interface_regex:
            interface_filter["name__regex"] = interface_regex
        try:
            nb_interfaces = list(nb.dcim.interfaces.filter(**interface_filter))
        except Exception as exc:
            msg = f"failed to fetch interfaces for description update: {exc}"
            log.error(msg)
            job.event(msg, severity="ERROR")
            ret.errors.append(msg)
            ret.failed = True
            return ret
        nb_interfaces_by_device = {}
        for nb_interface in nb_interfaces:
            nb_interfaces_by_device.setdefault(nb_interface.device.name, {})[
                nb_interface.name
            ] = nb_interface

        # produce interface description and update it
        for device in devices:
            device_connections = nb_connections.result.get(device, {})
            device_interfaces = nb_interfaces_by_device.get(device, {})
            interface_names = sorted(
                set(device_connections).union(device_interfaces)
            )
            if interfaces:
                interface_names = [
                    interface
                    for interface in interface_names
                    if interface in interfaces
                ]
            ret.result.setdefault(device, {})
            job.event(
                f"processing {len(interface_names)} interface(s) for '{device}'"
            )
            nb_device = nb.dcim.devices.get(name=device)
            for interface in interface_names:
                job.event(f"{device}:{interface} {update_action} description")
                connection = device_connections.get(interface)
                nb_interface = device_interfaces.get(interface)
                if connection is None:
                    if not nb_interface:
                        continue
                    connection = _empty_interface_connection_context(nb_interface)
                if connection["termination_type"] == "consoleport":
                    api_endpoint = nb.dcim.console_ports
                elif connection["termination_type"] == "consoleserverport":
                    api_endpoint = nb.dcim.console_server_ports
                elif connection["termination_type"] == "powerport":
                    api_endpoint = nb.dcim.power_ports
                elif connection["termination_type"] == "poweroutlet":
                    api_endpoint = nb.dcim.power_outlets
                else:
                    api_endpoint = nb.dcim.interfaces
                nb_interface = nb_interface or api_endpoint.get(
                    device=device, name=interface
                )
                if not nb_interface:
                    continue
                rendered_description = self.jinja2_render_templates(
                    templates=[description_template],
                    context={
                        "device": nb_device,
                        "interface": nb_interface,
                        **connection,
                    },
                )
                rendered_description = str(rendered_description).strip()
                ret.result[device][interface] = {
                    "-": str(nb_interface.description),
                    "+": rendered_description,
                }
                nb_interface.description = rendered_description
                if dry_run is False:
                    nb_interface.save()
    if descriptions:
        job.event(
            f"{apply_action} {len(descriptions)} description(s) "
            f"to {len(devices)} device(s)"
        )
        for device in devices:
            ret.result.setdefault(device, {})
            for interface, description in descriptions.items():
                nb_interface = nb.dcim.interfaces.get(name=interface, device=device)
                if nb_interface:
                    ret.result[device][interface] = {
                        "-": str(nb_interface.description),
                        "+": description,
                    }
                    nb_interface.description = description
                    if dry_run is False:
                        nb_interface.save()

    updated_count = sum(len(interfaces) for interfaces in ret.result.values())
    if dry_run is True:
        changed_count = sum(
            1
            for interfaces in ret.result.values()
            for diff in interfaces.values()
            if diff["-"] != diff["+"]
        )
        job.event(
            f"dry-run: would update {updated_count} interface description(s), "
            f"would change {changed_count}"
        )
    else:
        job.event(f"updated {updated_count} interface description(s)")
    job.event("interface description update task complete")
    return ret