1 | # py312 |
2 | # requirements: |
3 | # pynetbox==7.9.0 |
4 |
|
5 |
|
6 | from typing import List, Literal, Optional, TypedDict |
7 |
|
8 | import pynetbox |
9 |
|
10 |
|
11 | class netbox(TypedDict): |
12 | url: str |
13 | token: str |
14 |
|
15 |
|
16 | DynSelect_site_id = str |
17 | DynSelect_role_id = str |
18 |
|
19 |
|
20 | def api(auth: netbox): |
21 | # pynetbox adds /api itself; the resource URL may already end with it. |
22 | return pynetbox.api( |
23 | auth["url"].rstrip("/").removesuffix("/api"), token=auth["token"] |
24 | ) |
25 |
|
26 |
|
27 | def keep(**fields): |
28 | # The run form sends "" / [] / {} for untouched fields: drop them so a PATCH never blanks a value. |
29 | return {k: v for k, v in fields.items() if v not in (None, "", [], {})} |
30 |
|
31 |
|
32 | def options(endpoint, key="id", **filters): |
33 | return [ |
34 | {"value": str(getattr(r, key)), "label": r.display} |
35 | for r in endpoint.filter(brief=1, limit=1000, offset=0, **filters) |
36 | ] |
37 |
|
38 |
|
39 | # Dropdown of the instance's sites: the first 1000 (NetBox MAX_PAGE_SIZE); set the value by expression beyond that. |
40 | def site_id(auth: netbox): |
41 | return options(api(auth).dcim.sites) |
42 |
|
43 |
|
44 | # Dropdown of the instance's device roles. |
45 | def role_id(auth: netbox): |
46 | return options(api(auth).dcim.device_roles) |
47 |
|
48 |
|
49 | def main( |
50 | auth: netbox, |
51 | device_id: int, |
52 | name: Optional[str] = None, |
53 | status: Optional[ |
54 | Literal[ |
55 | "offline", |
56 | "active", |
57 | "planned", |
58 | "staged", |
59 | "failed", |
60 | "inventory", |
61 | "decommissioning", |
62 | ] |
63 | ] = None, |
64 | site_id: Optional[DynSelect_site_id] = None, |
65 | role_id: Optional[DynSelect_role_id] = None, |
66 | platform_id: Optional[int] = None, |
67 | serial: Optional[str] = None, |
68 | description: Optional[str] = None, |
69 | tags: Optional[List[str]] = None, |
70 | custom_fields: Optional[dict] = None, |
71 | ): |
72 | """ |
73 | Update Device |
74 | Patch a device: only the fields you set change. custom_fields merges into the existing values; tags (slugs) replace the current tags when set. |
75 | """ |
76 | body = keep( |
77 | name=name, |
78 | status=status, |
79 | site=int(site_id) if site_id else None, |
80 | role=int(role_id) if role_id else None, |
81 | platform=platform_id, |
82 | serial=serial, |
83 | description=description, |
84 | tags=[{"slug": s} for s in tags or []], |
85 | custom_fields=custom_fields, |
86 | ) |
87 | # A list body on the collection endpoint is NetBox's PATCH of only these fields. |
88 | return dict(api(auth).dcim.devices.update([{"id": device_id, **body}])[0]) |
89 | |