|
5 | 5 |
|
6 | 6 | from cliff.command import Command |
7 | 7 | from loguru import logger |
| 8 | +from tabulate import tabulate |
8 | 9 | import yaml |
9 | 10 |
|
10 | 11 | from osism.tasks import conductor, netbox, handle_task |
@@ -270,3 +271,157 @@ def take_action(self, parsed_args): |
270 | 271 | yaml.dump(nbcli_config, fp, default_flow_style=False) |
271 | 272 |
|
272 | 273 | subprocess.call(f"/usr/local/bin/nbcli {type_console} {arguments}", shell=True) |
| 274 | + |
| 275 | + |
| 276 | +class Show(Command): |
| 277 | + def get_parser(self, prog_name): |
| 278 | + parser = super(Show, self).get_parser(prog_name) |
| 279 | + parser.add_argument( |
| 280 | + "host", |
| 281 | + nargs=1, |
| 282 | + type=str, |
| 283 | + help="Hostname or device name to search in NetBox", |
| 284 | + ) |
| 285 | + parser.add_argument( |
| 286 | + "field", |
| 287 | + nargs="?", |
| 288 | + type=str, |
| 289 | + default=None, |
| 290 | + help="Optional field name filter (case-insensitive, partial match)", |
| 291 | + ) |
| 292 | + return parser |
| 293 | + |
| 294 | + def take_action(self, parsed_args): |
| 295 | + host = parsed_args.host[0] |
| 296 | + field_filter = parsed_args.field |
| 297 | + |
| 298 | + # Check if NetBox connection is available |
| 299 | + if not utils.nb: |
| 300 | + logger.error("NetBox integration not configured.") |
| 301 | + return |
| 302 | + |
| 303 | + # Search for device by name first |
| 304 | + devices = list(utils.nb.dcim.devices.filter(name=host)) |
| 305 | + |
| 306 | + # If not found by name, search by custom fields |
| 307 | + if not devices: |
| 308 | + # Search by alternative_name custom field |
| 309 | + devices = list(utils.nb.dcim.devices.filter(cf_alternative_name=host)) |
| 310 | + |
| 311 | + if not devices: |
| 312 | + # Search by inventory_hostname custom field |
| 313 | + devices = list(utils.nb.dcim.devices.filter(cf_inventory_hostname=host)) |
| 314 | + |
| 315 | + if not devices: |
| 316 | + # Search by external_hostname custom field |
| 317 | + devices = list(utils.nb.dcim.devices.filter(cf_external_hostname=host)) |
| 318 | + |
| 319 | + if not devices: |
| 320 | + logger.error(f"Device '{host}' not found in NetBox.") |
| 321 | + return |
| 322 | + |
| 323 | + # Get the first matching device |
| 324 | + device = devices[0] |
| 325 | + |
| 326 | + # Prepare table data for display |
| 327 | + table = [] |
| 328 | + |
| 329 | + # Add basic device information |
| 330 | + table.append(["Name", device.name]) |
| 331 | + |
| 332 | + # Device type - defensively accessed |
| 333 | + device_type = getattr(device, "device_type", None) |
| 334 | + table.append(["Device Type", str(device_type) if device_type else "N/A"]) |
| 335 | + |
| 336 | + # NetBox v3.x renamed device_role to role |
| 337 | + device_role = getattr(device, "role", None) |
| 338 | + table.append(["Device Role", str(device_role) if device_role else "N/A"]) |
| 339 | + |
| 340 | + # Site and status |
| 341 | + site = getattr(device, "site", None) |
| 342 | + table.append(["Site", str(site) if site else "N/A"]) |
| 343 | + |
| 344 | + status = getattr(device, "status", None) |
| 345 | + table.append(["Status", str(status) if status else "N/A"]) |
| 346 | + |
| 347 | + # Add out-of-band IP |
| 348 | + oob_ip = getattr(device, "oob_ip", None) |
| 349 | + table.append(["Out-of-band IP", str(oob_ip.address) if oob_ip else "N/A"]) |
| 350 | + |
| 351 | + # Add primary IPs - defensively accessed |
| 352 | + primary_ip4 = getattr(device, "primary_ip4", None) |
| 353 | + table.append( |
| 354 | + ["Primary IPv4", str(primary_ip4.address) if primary_ip4 else "N/A"] |
| 355 | + ) |
| 356 | + |
| 357 | + primary_ip6 = getattr(device, "primary_ip6", None) |
| 358 | + table.append( |
| 359 | + ["Primary IPv6", str(primary_ip6.address) if primary_ip6 else "N/A"] |
| 360 | + ) |
| 361 | + |
| 362 | + # Add custom fields if they exist - defensively accessed |
| 363 | + custom_fields = getattr(device, "custom_fields", {}) |
| 364 | + |
| 365 | + # Display custom field parameters with YAML formatting |
| 366 | + if custom_fields: |
| 367 | + # Define YAML custom fields for consistent formatting |
| 368 | + yaml_fields = [ |
| 369 | + "dnsmasq_parameters", |
| 370 | + "netplan_parameters", |
| 371 | + "sonic_parameters", |
| 372 | + "frr_parameters", |
| 373 | + ] |
| 374 | + |
| 375 | + for field_name in yaml_fields: |
| 376 | + field_value = custom_fields.get(field_name, None) |
| 377 | + if field_value: |
| 378 | + try: |
| 379 | + # Parse YAML string if needed, or use value directly if already parsed |
| 380 | + if isinstance(field_value, str): |
| 381 | + parsed_value = yaml.safe_load(field_value) |
| 382 | + else: |
| 383 | + parsed_value = field_value |
| 384 | + |
| 385 | + # Format as YAML with proper indentation and structure |
| 386 | + formatted_value = yaml.dump( |
| 387 | + parsed_value, |
| 388 | + default_flow_style=False, |
| 389 | + indent=2, |
| 390 | + sort_keys=False, |
| 391 | + width=80, |
| 392 | + ).strip() |
| 393 | + |
| 394 | + table.append([field_name, formatted_value]) |
| 395 | + except Exception: |
| 396 | + # Fallback to string representation if YAML parsing fails |
| 397 | + table.append([field_name, str(field_value)]) |
| 398 | + |
| 399 | + # alternative_name |
| 400 | + alternative_name = custom_fields.get("alternative_name", None) |
| 401 | + if alternative_name: |
| 402 | + table.append(["Alternative Name", str(alternative_name)]) |
| 403 | + |
| 404 | + # inventory_hostname |
| 405 | + inventory_hostname = custom_fields.get("inventory_hostname", None) |
| 406 | + if inventory_hostname: |
| 407 | + table.append(["Inventory Hostname", str(inventory_hostname)]) |
| 408 | + |
| 409 | + # external_hostname |
| 410 | + external_hostname = custom_fields.get("external_hostname", None) |
| 411 | + if external_hostname: |
| 412 | + table.append(["External Hostname", str(external_hostname)]) |
| 413 | + |
| 414 | + # Apply field filter if specified |
| 415 | + if field_filter: |
| 416 | + filter_term = field_filter.lower() |
| 417 | + filtered_table = [row for row in table if filter_term in row[0].lower()] |
| 418 | + |
| 419 | + if not filtered_table: |
| 420 | + logger.warning(f"No fields matching '{field_filter}' found") |
| 421 | + return |
| 422 | + |
| 423 | + table = filtered_table |
| 424 | + |
| 425 | + # Print formatted table |
| 426 | + result = tabulate(table, headers=["Field", "Value"], tablefmt="grid") |
| 427 | + print(result) |
0 commit comments