proxsync 2.0.8__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {proxsync-2.0.8/proxsync.egg-info → proxsync-2.1.0}/PKG-INFO +1 -1
  2. {proxsync-2.0.8 → proxsync-2.1.0/proxsync.egg-info}/PKG-INFO +1 -1
  3. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync.py +59 -11
  4. {proxsync-2.0.8 → proxsync-2.1.0}/pyproject.toml +1 -1
  5. {proxsync-2.0.8 → proxsync-2.1.0}/MANIFEST.in +0 -0
  6. {proxsync-2.0.8 → proxsync-2.1.0}/README.md +0 -0
  7. {proxsync-2.0.8 → proxsync-2.1.0}/config.py +0 -0
  8. {proxsync-2.0.8 → proxsync-2.1.0}/pbs_sync.py +0 -0
  9. {proxsync-2.0.8 → proxsync-2.1.0}/proxsync.egg-info/SOURCES.txt +0 -0
  10. {proxsync-2.0.8 → proxsync-2.1.0}/proxsync.egg-info/dependency_links.txt +0 -0
  11. {proxsync-2.0.8 → proxsync-2.1.0}/proxsync.egg-info/requires.txt +0 -0
  12. {proxsync-2.0.8 → proxsync-2.1.0}/proxsync.egg-info/top_level.txt +0 -0
  13. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/__init__.py +0 -0
  14. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/api/__init__.py +0 -0
  15. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/api/serializers.py +0 -0
  16. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/api/urls.py +0 -0
  17. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/api/views.py +0 -0
  18. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/apps.py +0 -0
  19. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/choices.py +0 -0
  20. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/filtersets.py +0 -0
  21. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/forms.py +0 -0
  22. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/management/__init__.py +0 -0
  23. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/management/commands/__init__.py +0 -0
  24. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/management/commands/pve_sync.py +0 -0
  25. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/management/commands/run_scheduled_syncs.py +0 -0
  26. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0001_initial.py +0 -0
  27. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0002_pvepluginsettings.py +0 -0
  28. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0003_netboxmodel_timestamps.py +0 -0
  29. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0004_model_verbose_names.py +0 -0
  30. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0005_align_netboxmodel_timestamps.py +0 -0
  31. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0006_pbsserverconfig_and_more.py +0 -0
  32. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0007_rename_verbose_names_to_proxmox_sync.py +0 -0
  33. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0008_pbsserverconfig_sync_schedule.py +0 -0
  34. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0009_add_every_3h_schedule.py +0 -0
  35. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0010_pvedriftevent.py +0 -0
  36. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py +0 -0
  37. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0012_pvedriftevent_tags_and_index_renames.py +0 -0
  38. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py +0 -0
  39. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0014_vmprovisioninglog.py +0 -0
  40. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0015_pvevmtasklog.py +0 -0
  41. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/0016_pvevmtasklog_status_textfield.py +0 -0
  42. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/migrations/__init__.py +0 -0
  43. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/models.py +0 -0
  44. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/navigation.py +0 -0
  45. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/signals.py +0 -0
  46. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/sync/__init__.py +0 -0
  47. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/sync/config_bridge.py +0 -0
  48. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/sync/engine.py +0 -0
  49. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/tables.py +0 -0
  50. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/tasks.py +0 -0
  51. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/template_content.py +0 -0
  52. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/dashboard.html +0 -0
  53. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html +0 -0
  54. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/inc/vm_task_log_panel.html +0 -0
  55. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pbsserverconfig.html +0 -0
  56. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pvebackupstatus.html +0 -0
  57. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pveclusterconfig.html +0 -0
  58. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pvedriftevent.html +0 -0
  59. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pvesyncjob.html +0 -0
  60. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pvevmtasklog_list.html +0 -0
  61. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/pvewebhookevent.html +0 -0
  62. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/settings.html +0 -0
  63. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/vm_provisioning_combined.html +0 -0
  64. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templates/pve_sync/vm_provisioning_log.html +0 -0
  65. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templatetags/__init__.py +0 -0
  66. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/templatetags/pve_sync_tags.py +0 -0
  67. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/urls.py +0 -0
  68. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/utils.py +0 -0
  69. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/view_helpers.py +0 -0
  70. {proxsync-2.0.8 → proxsync-2.1.0}/pve_sync_plugin/views.py +0 -0
  71. {proxsync-2.0.8 → proxsync-2.1.0}/setup.cfg +0 -0
  72. {proxsync-2.0.8 → proxsync-2.1.0}/state_db.py +0 -0
  73. {proxsync-2.0.8 → proxsync-2.1.0}/tests/test_plugin_contracts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxsync
3
- Version: 2.0.8
3
+ Version: 2.1.0
4
4
  Summary: NetBox plugin for Proxmox VE inventory synchronization
5
5
  Author-email: lixiujie85 <lixiujie85@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxsync
3
- Version: 2.0.8
3
+ Version: 2.1.0
4
4
  Summary: NetBox plugin for Proxmox VE inventory synchronization
5
5
  Author-email: lixiujie85 <lixiujie85@gmail.com>
6
6
  License-Expression: MIT
@@ -177,12 +177,32 @@ class OptimizedPVEToNetBoxSync:
177
177
  🔌 MAC 位址: {mac_address}
178
178
  ❌ 錯誤: {error_message}
179
179
 
180
- ⚠️ 該網卡的 MAC 位址與另一台已同步的 VM 重複(通常是複製 VM 時未重新產生 MAC),
181
- IP 位址仍會照常同步,但 MAC 關聯需要手動處理
180
+ ⚠️ 該網卡的 MAC 位址與另一台已同步的 VM 重複(通常是複製 VM 時未重新產生 MAC)
182
181
  """
183
182
  self.send_telegram_notification(message)
184
183
  print(f"📧 已發送 MAC 衝突通知: {vm_name} - {mac_address}")
185
184
 
185
+ def log_vm_sync_error(self, vm_name: str, vm_id: int, error_message: str):
186
+ error_info = {
187
+ 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S"),
188
+ 'vm_name': vm_name,
189
+ 'ip_address': f"vmid {vm_id}",
190
+ 'error': error_message
191
+ }
192
+ self.error_log.append(error_info)
193
+ message = f"""
194
+ 🚨 <b>PVE-NetBox 同步 VM 失敗警告</b>
195
+
196
+ 📅 時間: {error_info['timestamp']}
197
+ 🖥️ 虛擬機: {vm_name} (vmid {vm_id})
198
+ 🔀 叢集: {self.cluster_name}
199
+ ❌ 錯誤: {error_message}
200
+
201
+ ⚠️ 此 VM 本次同步未完成,下次同步會自動重試
202
+ """
203
+ self.send_telegram_notification(message)
204
+ print(f"📧 已發送 VM 同步失敗通知: {vm_name} - {error_message}")
205
+
186
206
  def send_enhanced_summary(self):
187
207
  if not hasattr(self, 'stats'):
188
208
  return
@@ -657,7 +677,8 @@ class OptimizedPVEToNetBoxSync:
657
677
  return None
658
678
 
659
679
  # ---------- IP 分配(改進版) ----------
660
- def assign_ip_to_interface(self, interface, ip_address: str, dns_name: str = None, is_vm_interface: bool = False) -> Optional[Any]:
680
+ def assign_ip_to_interface(self, interface, ip_address: str, dns_name: str = None,
681
+ is_vm_interface: bool = False, owner_name: str = None) -> Optional[Any]:
661
682
  try:
662
683
  if '/' not in ip_address:
663
684
  ip_with_prefix = f"{ip_address}/24"
@@ -705,16 +726,24 @@ class OptimizedPVEToNetBoxSync:
705
726
  except Exception as e:
706
727
  error_msg = str(e)
707
728
  print(f" 分配 IP 位址失敗 {ip_address}: {error_msg}")
708
- if "Cannot reassign IP address while it is designated as the primary IP" in error_msg:
709
- vm_name = "Unknown"
729
+ # Any IP assignment failure needs a human to look at it, not just the
730
+ # one specific "reassign primary IP" error — otherwise conflicts with a
731
+ # different underlying cause (VRF/prefix validation, overlapping range,
732
+ # etc.) fail silently with nothing but a stdout line.
733
+ owner = owner_name or "Unknown"
734
+ if owner == "Unknown":
710
735
  try:
711
736
  if is_vm_interface:
712
737
  vm_info = self.nb_api.virtualization.interfaces.get(interface.id)
713
738
  if vm_info and vm_info.virtual_machine:
714
- vm_name = vm_info.virtual_machine.name
715
- except:
739
+ owner = vm_info.virtual_machine.name
740
+ else:
741
+ dev_info = self.nb_api.dcim.interfaces.get(interface.id)
742
+ if dev_info and dev_info.device:
743
+ owner = dev_info.device.name
744
+ except Exception:
716
745
  pass
717
- self.log_ip_conflict_error(vm_name, ip_address, error_msg)
746
+ self.log_ip_conflict_error(owner, ip_address, error_msg)
718
747
  return None
719
748
 
720
749
  # ---------- 節點網路介面同步 ----------
@@ -770,7 +799,8 @@ class OptimizedPVEToNetBoxSync:
770
799
  continue
771
800
  cidr = iface_data.get('cidr')
772
801
  if cidr:
773
- self.assign_ip_to_interface(nb_iface, cidr, dns_name=f"{node_name}.local", is_vm_interface=False)
802
+ self.assign_ip_to_interface(nb_iface, cidr, dns_name=f"{node_name}.local",
803
+ is_vm_interface=False, owner_name=device.name)
774
804
 
775
805
  # ---------- 節點同步 ----------
776
806
  def sync_pve_nodes_to_netbox(self) -> Tuple[bool, Dict[str, Any], Dict]:
@@ -1046,7 +1076,8 @@ class OptimizedPVEToNetBoxSync:
1046
1076
  prefix_len = ip_info['prefix']
1047
1077
  full_addr = f"{ip_addr}/{prefix_len}"
1048
1078
  ip_obj = self.assign_ip_to_interface(
1049
- vm_interface, full_addr, f"{vm.name}.local", is_vm_interface=True
1079
+ vm_interface, full_addr, f"{vm.name}.local",
1080
+ is_vm_interface=True, owner_name=vm.name
1050
1081
  )
1051
1082
  if ip_obj and not primary_ip:
1052
1083
  primary_ip = ip_obj
@@ -1770,13 +1801,29 @@ class OptimizedPVEToNetBoxSync:
1770
1801
  print(f" ✓ VM {original_vm_name} 電源狀態更新: {current_status} → {status}")
1771
1802
  except Exception as e:
1772
1803
  print(f" ✗ 更新 VM {original_vm_name} 關聯失敗: {e}")
1804
+ # MAC/IP 衝突檢查不受增量同步影響:即使 PVE 配置沒變,仍要每次
1805
+ # 重新檢查介面的 MAC/IP 位址,衝突就再次通知,而不是只在第一次配置
1806
+ # 變更時才發現。process_vm_interfaces 本身是冪等的(已存在的 MAC/IP
1807
+ # 不會重複建立),所以在跳過分支呼叫它不會產生多餘的 NetBox 寫入。
1808
+ _, skip_primary_ip, _ = self.process_vm_interfaces(
1809
+ cached_vm, vm_config, agent_interfaces, mac_to_interface, device
1810
+ )
1811
+ if skip_primary_ip:
1812
+ try:
1813
+ current_primary_ip4_id = getattr(getattr(cached_vm, 'primary_ip4', None), 'id', None)
1814
+ if current_primary_ip4_id != skip_primary_ip.id:
1815
+ cached_vm.primary_ip4 = skip_primary_ip.id
1816
+ cached_vm.save()
1817
+ except Exception as e:
1818
+ print(f" 設定 VM 主 IP 失敗: {e}")
1773
1819
  print(f" ℹ️ VM {original_vm_name} 無配置變更,跳過同步")
1774
1820
  if self.state_db:
1775
1821
  try:
1776
1822
  config_hash = compute_config_hash(vm_config, tag_names, network_interfaces)
1777
1823
  memory = int(vm_config.get('memory', 0))
1778
1824
  vcpus_save = int(vm_config.get('vcpus', vcpus))
1779
- cur_ip = getattr(getattr(cached_vm, 'primary_ip4', None), 'address', None) or ''
1825
+ cur_ip = getattr(skip_primary_ip, 'address', None) \
1826
+ or getattr(getattr(cached_vm, 'primary_ip4', None), 'address', None) or ''
1780
1827
  self.state_db.save_vm_config_snapshot(
1781
1828
  vm_id=int(vm_id), cluster_name=self.cluster_name, config_hash=config_hash,
1782
1829
  memory=memory, vcpus=vcpus_save, tags=tag_names,
@@ -1901,6 +1948,7 @@ class OptimizedPVEToNetBoxSync:
1901
1948
  except Exception as e:
1902
1949
  error_msg = str(e)
1903
1950
  print(f" 處理虛擬機失敗: {error_msg}")
1951
+ self.log_vm_sync_error(original_vm_name, int(vm_id), error_msg)
1904
1952
  # 即使 NetBox 同步失敗,仍需更新 snapshot 的非 hash 欄位(vm_name, node,
1905
1953
  # disk_summary, description, tags, memory, vcpus),並保留舊 config_hash,
1906
1954
  # 使下一輪不重複發送漂移通知,同時 should_sync_vm() 仍會返回 True 繼續重試。
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proxsync"
7
- version = "2.0.8"
7
+ version = "2.1.0"
8
8
  description = "NetBox plugin for Proxmox VE inventory synchronization"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes