Skip to content

Netbox Sync All Task¤

task api name: sync_all

The sync_all task synchronizes device data from live devices into NetBox in a fixed sequence:

  1. inventory — calls sync_device_inventory
  2. interfaces — calls sync_device_interfaces
  3. mac_addresses — calls sync_mac_addresses
  4. ip_addresses — calls sync_device_ip
  5. bgp_peerings — calls sync_bgp_peerings

How It Works¤

The sync_all task orchestrates five subordinate sync tasks in sequence. Each task collects live device data, compares it against NetBox state, and applies reconciliation operations. When dry_run=True, all tasks preview changes without writing. When with_approval=True, each stage waits for user confirmation before applying changes.

Execution Modes¤

Dry-run mode (dry_run=True) previews all changes without writing to NetBox.

With Approval — Pass with_approval=True to use the interactive NFCLI workflow. Each sync stage displays its preview, and waits for review before applying that stage. Declining a stage stops sync_all at that point, returns the declined dry-run result, and skips later stages. Any earlier approved stages remain applied.

Note

When both dry-run and with_approval are True, dry-run logic ignored.

Live-run mode (dry_run=False, default) applies all changes to NetBox.

Inventory Arguments¤

Inventory-specific options use an inventory_ prefix in the Python API and an inventory- prefix in NFCLI:

Python argument Purpose
inventory_create_module_types Create missing NetBox module types.
inventory_create_module_bays Create missing NetBox module bays.
inventory_map Inline mapping or nf:// YAML mapping file.
inventory_transform nf:// Python transformer file.
inventory_filter_by_module Include normalized module types matching glob patterns.
inventory_filter_by_slot Include normalized module bays matching glob patterns.
inventory_ignore_modules Exclude normalized module types matching glob patterns.
inventory_ignore_slots Exclude normalized module bays matching glob patterns.

The shared process_deletions argument controls deletion behavior for inventory, interfaces, and BGP peerings.

The shared message argument is used as the NetBox changelog message for both inventory and BGP write operations.

Output¤

The result structure aggregates the outcomes of all five subordinate sync tasks. When dry_run=True the same structure is returned but no changes are written to NetBox.

{
    # per-device results — one entry per resolved device
    "result": {
        "ceos-spine-1": {
            "inventory": {
                "created": [ ... ],
                "updated": [ ... ],
                "deleted": [ ... ],
                "in_sync": [ ... ],
            },
            "interfaces": {
                "create":  { ... },
                "update":  { ... },
                "delete":  { ... },
                "in_sync": [ ... ],
            },
            "mac_addresses": {
                "created": [ ... ],
                "updated": [ ... ],
                "in_sync": [ ... ],
            },
            "ip_addresses": {
                "created": [ ... ],
                "updated": [ ... ],
                "in_sync": [ ... ],
            },
            "bgp_peerings": {
                "create":  { ... },
                "update":  { ... },
                "delete":  { ... },
                "in_sync": [ ... ],
            },
        },
        ...
    },
    "diff": {},
}

When dry_run=True the same structure is returned but no changes are written to NetBox.

Examples¤

Preview all five sync categories:

nf#netbox sync all devices ceos-spine-1 ceos-spine-2 dry-run

Preview, prompt for review, and apply the changes:

nf#netbox sync all devices ceos-spine-1 ceos-spine-2 with-approval

Create missing module bays and module types during inventory sync:

nf#netbox sync all devices iosxr1 inventory-create-module-bays inventory-create-module-types

Use mapping and transformer files:

nf#netbox sync all devices iosxr1 inventory-map nf://netbox/inventory_maps/iosxr.yaml inventory-transform nf://netbox/inventory_transformers/iosxr.py dry-run

Limit inventory sync to selected normalized modules and slots:

nf#netbox sync all devices iosxr1 inventory-filter-by-module "A9K-*" inventory-filter-by-slot "module 0/*" inventory-ignore-modules "SFP-*"

Enable deletions for inventory, interfaces, and BGP peerings:

nf#netbox sync all devices iosxr1 process-deletions
result = client.run_job(
    "netbox",
    "sync_all",
    workers="any",
    kwargs={
        "devices": ["iosxr1"],
        "dry_run": True,
        "inventory_create_module_bays": True,
        "inventory_create_module_types": True,
        "inventory_map": "nf://netbox/inventory_maps/iosxr.yaml",
        "inventory_transform": (
            "nf://netbox/inventory_transformers/iosxr.py"
        ),
        "inventory_filter_by_module": ["A9K-*"],
        "inventory_filter_by_slot": ["module 0/*"],
        "inventory_ignore_modules": ["SFP-*"],
        "inventory_ignore_slots": ["power-module *"],
        "message": "sync all device data",
    },
)

NORFAB Netbox Sync All Command Shell Reference¤

NorFab shell supports these command options for Netbox sync_all task:

nf# man tree netbox.sync.all
root
└── netbox:    Netbox service
    └── sync:    Sync Netbox data
        └── all:    Sync device inventory, interfaces, MAC addresses, IP addresses and BGP peerings
            ├── 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
            ├── devices:    List of NetBox devices to sync all data for
            ├── dry-run:    Return diff without writing to NetBox, default 'False'
            ├── with-approval:    Preview each sync stage and ask for review before writing to NetBox
            ├── process-deletions:    Process deletions for inventory, interfaces and BGP peerings
            ├── message:    Changelog message for inventory and BGP operations
            ├── inventory-create-module-types:    Create missing module types during inventory sync
            ├── inventory-create-module-bays:    Create missing module bays during inventory sync
            ├── inventory-map:    Inventory pattern mappings or nf:// YAML file reference
            ├── inventory-transform:    nf:// Python inventory transformer file
            ├── inventory-filter-by-module:    Glob patterns selecting normalized module type names
            ├── inventory-filter-by-slot:    Glob patterns selecting normalized module bay names
            ├── inventory-ignore-modules:    Glob patterns excluding normalized module type names
            ├── inventory-ignore-slots:    Glob patterns excluding normalized module bay names
            ├── FO:    Filter hosts using Filter Object
            ├── FB:    Filter hosts by name using Glob Patterns
            ├── FH:    Filter hosts by hostname
            ├── FC:    Filter hosts containment of pattern in name
            ├── FR:    Filter hosts by name using Regular Expressions
            ├── FG:    Filter hosts by group
            ├── FP:    Filter hosts by hostname using IP Prefix
            ├── FL:    Filter hosts by names list
            ├── FX:    Filter hosts excluding them by name
            └── FN:    Negate the match
nf#

Python API Reference¤

Synchronize all device data from live devices into NetBox in sequence: inventory → interfaces → MAC addresses → IP addresses → BGP peerings.

Pass dry_run=True to preview changes without writing to NetBox. Pass with_approval=True to have each sync stage run a dry-run preview, ask the interactive client for review, and only then apply that stage.

Result.result is keyed by device name, then by category::

{
    "<device>": {
        "inventory":     {"created": [...], "updated": [...], "deleted": [...], "in_sync": [...]},
        "interfaces":    {"created": [...], "updated": {...}, "deleted": [...], "in_sync": [...]},
        "mac_addresses": {"created": [...], "updated": [...], "in_sync": [...]},
        "ip_addresses":  {"created": [...], "updated": [...], "in_sync": [...]},
        "bgp_peerings":  {"create": [...],  "update": {...},  "delete": [...],  "in_sync": [...]},
    }
}

Parameters:

Name Type Description Default
job Job

NorFab Job object.

required
instance str

NetBox instance name.

None
timeout int

Timeout in seconds for Nornir jobs. Defaults to 60.

600
devices list

List of device names to sync.

None
branch str

NetBox branching plugin branch name.

None
dry_run bool

If True, preview changes without writing to NetBox. Defaults to False.

False
with_approval bool

Preview and ask for review before applying each sync stage. Defaults to False.

False
process_deletions bool

Process deletions for inventory, interfaces, and BGP peerings. Defaults to False.

False
message str

Changelog message for inventory and BGP operations.

None
inventory_create_module_types bool

Create missing module types.

False
inventory_create_module_bays bool

Create missing module bays.

False
inventory_map Union[None, str, dict, InventoryPatternMap]

Inventory mappings or nf:// YAML file reference.

None
inventory_transform str

nf:// Python transformer.

None
inventory_filter_by_module list

Module type glob filters.

None
inventory_filter_by_slot list

Module bay glob filters.

None
inventory_ignore_modules list

Module type ignore globs.

None
inventory_ignore_slots list

Module bay ignore globs.

None
interfaces_filter_by_name str

Glob pattern to filter interfaces by name.

None
interfaces_filter_by_description str

Glob pattern to filter interfaces by description.

None
interfaces_update_type bool

Update existing NetBox interface types.

False
interfaces_vlan_group (str, int)

VLAN group name, slug, or ID for interface VLAN resolution.

None
mac_filter_by_name str

Glob pattern to filter MAC sync interfaces by name.

None
mac_filter_by_description str

Glob pattern to filter MAC sync interfaces by description.

None
mac_filter_by_mac str

Glob pattern to filter MAC addresses.

None
ip_anycast_ranges list

IP prefixes to classify as anycast.

None
ip_ignore_ranges list

IP prefixes to exclude from IP sync.

None
ip_create_prefixes bool

Create missing prefixes during IP sync.

True
ip_filter_by_name str

Glob pattern to filter IP sync interfaces by name.

None
ip_filter_by_description str

Glob pattern to filter IP sync interfaces by description.

None
ip_filter_by_prefix str

IP prefix to restrict synced IP addresses.

None
ip_filter_by_ip str

Glob pattern to restrict synced IP addresses.

None
bgp_status str

Status to set on created/updated BGP sessions.

'active'
bgp_rir str

RIR name to use when creating new ASNs.

None
bgp_name_template str

Template string for BGP session names.

'{device}_{name}'
bgp_filter_by_remote_as list

Only sync sessions matching remote AS numbers.

None
bgp_filter_by_peer_group list

Only sync sessions matching peer groups.

None
bgp_filter_by_description str

Only sync sessions matching description glob.

None
bgp_ignore_peer_ranges list

Prefixes to ignore BGP peers.

None
bgp_vrf_custom_field str

BGP session custom field name used to store VRF reference.

'vrf'
**kwargs Any

Nornir host filter arguments (e.g. FL, FC, FB).

{}

Returns:

Name Type Description
Result Result

Per-device sync results keyed by device name and category.

Source code in norfab\workers\netbox_worker\devices_tasks.py
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
@Task(
    fastapi={"methods": ["PATCH"], "schema": NetboxFastApiArgs.model_json_schema()},
    input=SyncAllInput,
    output=SyncAllResult,
    mcp={
        "annotations": {
            "title": "Sync All Device Data",
            "readOnlyHint": False,
            "destructiveHint": True,
            "idempotentHint": True,
            "openWorldHint": True,
        }
    },
)
def sync_all(
    self,
    job: Job,
    instance: Union[None, str] = None,
    timeout: int = 600,
    devices: Union[None, list] = None,
    branch: str = None,
    dry_run: bool = False,
    with_approval: bool = False,
    process_deletions: bool = False,
    message: Union[None, str] = None,
    inventory_create_module_types: bool = False,
    inventory_create_module_bays: bool = False,
    inventory_map: Union[None, str, dict, InventoryPatternMap] = None,
    inventory_transform: Union[None, str] = None,
    inventory_filter_by_module: Union[None, list] = None,
    inventory_filter_by_slot: Union[None, list] = None,
    inventory_ignore_modules: Union[None, list] = None,
    inventory_ignore_slots: Union[None, list] = None,
    interfaces_filter_by_name: Union[None, str] = None,
    interfaces_filter_by_description: Union[None, str] = None,
    interfaces_update_type: Union[None, bool] = False,
    interfaces_vlan_group: Union[None, str, int] = None,
    mac_filter_by_name: Union[None, str] = None,
    mac_filter_by_description: Union[None, str] = None,
    mac_filter_by_mac: Union[None, str] = None,
    ip_anycast_ranges: Union[None, list] = None,
    ip_ignore_ranges: Union[None, list] = None,
    ip_create_prefixes: bool = True,
    ip_filter_by_name: Union[None, str] = None,
    ip_filter_by_description: Union[None, str] = None,
    ip_filter_by_prefix: Union[None, str] = None,
    ip_filter_by_ip: Union[None, str] = None,
    bgp_status: str = "active",
    bgp_rir: Union[None, str] = None,
    bgp_name_template: str = "{device}_{name}",
    bgp_filter_by_remote_as: Union[None, list] = None,
    bgp_filter_by_peer_group: Union[None, list] = None,
    bgp_filter_by_description: Union[None, str] = None,
    bgp_ignore_peer_ranges: Union[None, list] = None,
    bgp_vrf_custom_field: str = "vrf",
    **kwargs: Any,
) -> Result:
    """
    Synchronize all device data from live devices into NetBox in sequence:
    inventory → interfaces → MAC addresses → IP addresses → BGP peerings.

    Pass ``dry_run=True`` to preview changes without writing to NetBox.
    Pass ``with_approval=True`` to have each sync stage run a dry-run preview,
    ask the interactive client for review, and only then apply that stage.

    ``Result.result`` is keyed by device name, then by category::

        {
            "<device>": {
                "inventory":     {"created": [...], "updated": [...], "deleted": [...], "in_sync": [...]},
                "interfaces":    {"created": [...], "updated": {...}, "deleted": [...], "in_sync": [...]},
                "mac_addresses": {"created": [...], "updated": [...], "in_sync": [...]},
                "ip_addresses":  {"created": [...], "updated": [...], "in_sync": [...]},
                "bgp_peerings":  {"create": [...],  "update": {...},  "delete": [...],  "in_sync": [...]},
            }
        }

    Args:
        job: NorFab Job object.
        instance (str, optional): NetBox instance name.
        timeout (int): Timeout in seconds for Nornir jobs. Defaults to 60.
        devices (list, optional): List of device names to sync.
        branch (str, optional): NetBox branching plugin branch name.
        dry_run (bool): If True, preview changes without writing to NetBox. Defaults to False.
        with_approval (bool): Preview and ask for review before applying
            each sync stage. Defaults to False.
        process_deletions (bool): Process deletions for inventory,
            interfaces, and BGP peerings. Defaults to False.
        message (str, optional): Changelog message for inventory and BGP
            operations.
        inventory_create_module_types (bool): Create missing module types.
        inventory_create_module_bays (bool): Create missing module bays.
        inventory_map: Inventory mappings or ``nf://`` YAML file reference.
        inventory_transform (str, optional): ``nf://`` Python transformer.
        inventory_filter_by_module (list, optional): Module type glob filters.
        inventory_filter_by_slot (list, optional): Module bay glob filters.
        inventory_ignore_modules (list, optional): Module type ignore globs.
        inventory_ignore_slots (list, optional): Module bay ignore globs.
        interfaces_filter_by_name (str, optional): Glob pattern to filter interfaces by name.
        interfaces_filter_by_description (str, optional): Glob pattern to filter interfaces by description.
        interfaces_update_type (bool, optional): Update existing NetBox interface types.
        interfaces_vlan_group (str, int, optional): VLAN group name, slug, or ID for interface VLAN resolution.
        mac_filter_by_name (str, optional): Glob pattern to filter MAC sync interfaces by name.
        mac_filter_by_description (str, optional): Glob pattern to filter MAC sync interfaces by description.
        mac_filter_by_mac (str, optional): Glob pattern to filter MAC addresses.
        ip_anycast_ranges (list, optional): IP prefixes to classify as anycast.
        ip_ignore_ranges (list, optional): IP prefixes to exclude from IP sync.
        ip_create_prefixes (bool): Create missing prefixes during IP sync.
        ip_filter_by_name (str, optional): Glob pattern to filter IP sync interfaces by name.
        ip_filter_by_description (str, optional): Glob pattern to filter IP sync interfaces by description.
        ip_filter_by_prefix (str, optional): IP prefix to restrict synced IP addresses.
        ip_filter_by_ip (str, optional): Glob pattern to restrict synced IP addresses.
        bgp_status (str): Status to set on created/updated BGP sessions.
        bgp_rir (str, optional): RIR name to use when creating new ASNs.
        bgp_name_template (str): Template string for BGP session names.
        bgp_filter_by_remote_as (list, optional): Only sync sessions matching remote AS numbers.
        bgp_filter_by_peer_group (list, optional): Only sync sessions matching peer groups.
        bgp_filter_by_description (str, optional): Only sync sessions matching description glob.
        bgp_ignore_peer_ranges (list, optional): Prefixes to ignore BGP peers.
        bgp_vrf_custom_field (str): BGP session custom field name used to store VRF reference.
        **kwargs: Nornir host filter arguments (e.g. ``FL``, ``FC``, ``FB``).

    Returns:
        Result: Per-device sync results keyed by device name and category.
    """
    devices = devices or []
    instance = instance or self.default_instance
    ret = Result(
        task=f"{self.name}:sync_all",
        result={},
        resources=[instance],
        diff={},
        dry_run=dry_run,
    )

    # resolve devices from Nornir filters
    if kwargs:
        nornir_hosts = self.get_nornir_hosts(kwargs, timeout)
        for host in nornir_hosts:
            if host not in devices:
                devices.append(host)

    if not devices:
        ret.errors.append("no devices specified")
        ret.failed = True
        return ret

    log.info(
        f"{self.name} - Sync all for {len(devices)} device(s) in '{instance}', dry_run={dry_run}"
    )
    job.event(f"SYNCING all data for {len(devices)} device(s), dry_run={dry_run}")

    # initialize per-device result structure
    for device in devices:
        ret.result[device] = {}

    # --- sync device inventory ---
    job.event("SYNCING device inventory")
    inventory_result = self.sync_device_inventory(
        job=job,
        instance=instance,
        dry_run=dry_run,
        timeout=timeout,
        devices=list(devices),
        branch=branch,
        with_approval=with_approval,
        process_deletions=process_deletions,
        create_module_types=inventory_create_module_types,
        create_module_bays=inventory_create_module_bays,
        inventory_map=inventory_map,
        inventory_transform=inventory_transform,
        filter_by_module=inventory_filter_by_module,
        filter_by_slot=inventory_filter_by_slot,
        ignore_modules=inventory_ignore_modules,
        ignore_slots=inventory_ignore_slots,
        message=message,
    )
    if inventory_result.errors:
        job.event("inventory sync completed with errors", severity="WARNING")
        ret.errors.extend(inventory_result.errors)
    for device, data in inventory_result.result.items():
        ret.result.setdefault(device, {})["inventory"] = data
    if inventory_result.status == "skipped" and inventory_result.dry_run:
        ret.status = "skipped"
        ret.dry_run = True
        job.event("sync all stopped because device inventory review was declined")
        return ret

    # --- sync interfaces ---
    job.event("SYNCING interfaces")
    intf_result = self.sync_device_interfaces(
        job=job,
        instance=instance,
        dry_run=dry_run,
        timeout=timeout,
        devices=list(devices),
        branch=branch,
        process_deletions=process_deletions,
        with_approval=with_approval,
        filter_by_name=interfaces_filter_by_name,
        filter_by_description=interfaces_filter_by_description,
        update_type=interfaces_update_type,
        vlan_group=interfaces_vlan_group,
    )
    if intf_result.errors:
        job.event("interface sync completed with errors", severity="WARNING")
        ret.errors.extend(intf_result.errors)
    for device, data in intf_result.result.items():
        ret.result.setdefault(device, {})["interfaces"] = data
    if intf_result.status == "skipped" and intf_result.dry_run:
        ret.status = "skipped"
        ret.dry_run = True
        job.event("sync all stopped because interface review was declined")
        return ret

    # --- sync MAC addresses ---
    job.event("SYNCING MAC addresses")
    mac_result = self.sync_mac_addresses(
        job=job,
        instance=instance,
        dry_run=dry_run,
        timeout=timeout,
        devices=list(devices),
        branch=branch,
        with_approval=with_approval,
        filter_by_name=mac_filter_by_name,
        filter_by_description=mac_filter_by_description,
        filter_by_mac=mac_filter_by_mac,
    )
    if mac_result.errors:
        job.event("MAC address sync completed with errors", severity="WARNING")
        ret.errors.extend(mac_result.errors)
    for device, data in mac_result.result.items():
        ret.result.setdefault(device, {})["mac_addresses"] = data
    if mac_result.status == "skipped" and mac_result.dry_run:
        ret.status = "skipped"
        ret.dry_run = True
        job.event("sync all stopped because MAC address review was declined")
        return ret

    # --- sync IP addresses ---
    job.event("SYNCING IP addresses")
    ip_result = self.sync_device_ip(
        job=job,
        instance=instance,
        dry_run=dry_run,
        timeout=timeout,
        devices=list(devices),
        branch=branch,
        with_approval=with_approval,
        anycast_ranges=ip_anycast_ranges,
        ignore_ranges=ip_ignore_ranges,
        create_prefixes=ip_create_prefixes,
        filter_by_name=ip_filter_by_name,
        filter_by_description=ip_filter_by_description,
        filter_by_prefix=ip_filter_by_prefix,
        filter_by_ip=ip_filter_by_ip,
    )
    if ip_result.errors:
        job.event("IP address sync completed with errors", severity="WARNING")
        ret.errors.extend(ip_result.errors)
    for device, data in ip_result.result.items():
        ret.result.setdefault(device, {})["ip_addresses"] = data
    if ip_result.status == "skipped" and ip_result.dry_run:
        ret.status = "skipped"
        ret.dry_run = True
        job.event("sync all stopped because IP address review was declined")
        return ret

    # --- sync BGP peerings ---
    job.event("SYNCING BGP peerings")
    bgp_result = self.sync_bgp_peerings(
        job=job,
        instance=instance,
        status=bgp_status,
        dry_run=dry_run,
        timeout=timeout,
        devices=list(devices),
        branch=branch,
        process_deletions=process_deletions,
        with_approval=with_approval,
        rir=bgp_rir,
        message=message,
        name_template=bgp_name_template,
        filter_by_remote_as=bgp_filter_by_remote_as,
        filter_by_peer_group=bgp_filter_by_peer_group,
        filter_by_description=bgp_filter_by_description,
        ignore_peer_ranges=bgp_ignore_peer_ranges,
        vrf_custom_field=bgp_vrf_custom_field,
    )
    if bgp_result.errors:
        job.event("BGP peerings sync completed with errors", severity="WARNING")
        ret.errors.extend(bgp_result.errors)
    for device, data in bgp_result.result.items():
        ret.result.setdefault(device, {})["bgp_peerings"] = data
    if bgp_result.status == "skipped" and bgp_result.dry_run:
        ret.status = "skipped"
        ret.dry_run = True
        job.event("sync all stopped because BGP peerings review was declined")
        return ret

    log.info(f"{self.name} - Sync all complete for {len(ret.result)} device(s)")
    job.event(f"sync all complete for {len(ret.result)} device(s)")
    return ret