annet 0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

Files changed (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
annet/__init__.py ADDED
@@ -0,0 +1,61 @@
1
+ import logging
2
+ import logging.config
3
+ import os
4
+ import pkgutil
5
+ import sys
6
+ from argparse import SUPPRESS, Namespace
7
+
8
+ import colorama
9
+ import yaml
10
+ from annet.annlib.errors import ( # pylint: disable=wrong-import-position
11
+ DeployCancelled,
12
+ ExecError,
13
+ )
14
+ from contextlog import patch_logging, patch_threading
15
+ from valkit.python import valid_logging_level
16
+
17
+ import annet.argparse
18
+
19
+
20
+ __all__ = ("DeployCancelled", "ExecError")
21
+
22
+ DEBUG2_LEVELV_NUM = 9
23
+
24
+
25
+ def fill_base_args(parser: annet.argparse.ArgParser, pkg_name: str, logging_config: str):
26
+ parser.add_argument("--log-level", default="WARN", type=valid_logging_level,
27
+ help="Уровень детализации логов (DEBUG, DEBUG2 (with comocutor debug), INFO, WARN, CRITICAL)")
28
+ parser.add_argument("--pkg_name", default=pkg_name, help=SUPPRESS)
29
+ parser.add_argument("--logging_config", default=logging_config, help=SUPPRESS)
30
+
31
+
32
+ def init_logging(options: Namespace):
33
+ patch_logging()
34
+ patch_threading()
35
+ logging.captureWarnings(True)
36
+ logging_config = yaml.safe_load(pkgutil.get_data(options.pkg_name, options.logging_config))
37
+ if options.log_level is not None:
38
+ logging_config.setdefault("root", {})
39
+ logging_config["root"]["level"] = options.log_level
40
+ logging.addLevelName(DEBUG2_LEVELV_NUM, "DEBUG2")
41
+ logging.config.dictConfig(logging_config)
42
+
43
+
44
+ def init(options: Namespace):
45
+ init_logging(options)
46
+
47
+ # Отключить colorama.init, если стоит env-переменная. Нужно в тестах
48
+ if os.environ.get("ANN_FORCE_COLOR", None) not in [None, "", "0", "no"]:
49
+ colorama.init = lambda *_, **__: None
50
+ colorama.init()
51
+
52
+ # Workaround for Python 3.8.0: https://bugs.python.org/issue38529
53
+ import asyncio.streams
54
+ if hasattr(asyncio.streams.StreamReaderProtocol, "_on_reader_gc"):
55
+ asyncio.streams.StreamReaderProtocol._on_reader_gc = lambda *args, **kwargs: None # pylint: disable=protected-access
56
+
57
+
58
+ def assert_python_version():
59
+ if sys.version_info < (3, 8, 0):
60
+ sys.stderr.write("Error: you need python 3.8.0 or higher\n")
61
+ sys.exit(1)
File without changes
File without changes
File without changes
@@ -0,0 +1,87 @@
1
+ from dataclasses import dataclass
2
+ from functools import wraps
3
+ from typing import Generic, Optional, List, TypeVar, Callable
4
+
5
+ from dataclass_rest.http.requests import RequestsClient
6
+ from requests import Session
7
+
8
+ Model = TypeVar("Model")
9
+
10
+
11
+ @dataclass
12
+ class PagingResponse(Generic[Model]):
13
+ next: Optional[str]
14
+ previous: Optional[str]
15
+ count: int
16
+ results: List[Model]
17
+
18
+
19
+ Func = TypeVar("Func", bound=Callable)
20
+
21
+
22
+ def _collect_by_pages(func: Func) -> Func:
23
+ """Collect all results using only pagination."""
24
+ @wraps(func)
25
+ def wrapper(self, *args, **kwargs):
26
+ kwargs.setdefault("offset", 0)
27
+ limit = kwargs.setdefault("limit", 100)
28
+ results = []
29
+ method = func.__get__(self, self.__class__)
30
+ has_next = True
31
+ while has_next:
32
+ page = method(*args, **kwargs)
33
+ kwargs["offset"] += limit
34
+ results.extend(page.results)
35
+ has_next = bool(page.next)
36
+ return PagingResponse(
37
+ previous=None,
38
+ next=None,
39
+ count=len(results),
40
+ results=results,
41
+ )
42
+
43
+ return wrapper
44
+
45
+
46
+ # default batch size 100 is calculated to fit list of UUIDs in 4k URL length
47
+ def collect(func: Func, field: str = "", batch_size: int = 100) -> Func:
48
+ """
49
+ Collect data from method iterating over pages and filter batches.
50
+
51
+ :param func: Method to call
52
+ :param field: Field which defines a filter split into batches
53
+ :param batch_size: Limit of values in `field` filter requested at a time
54
+ """
55
+ func = _collect_by_pages(func)
56
+ if not field:
57
+ return func
58
+
59
+ @wraps(func)
60
+ def wrapper(self, *args, **kwargs):
61
+ value = kwargs.get(field)
62
+ if not value:
63
+ return func(*args, **kwargs)
64
+
65
+ method = func.__get__(self, self.__class__)
66
+ results = []
67
+ for offset in range(0, len(value), batch_size):
68
+ kwargs[field] = value[offset:offset + batch_size]
69
+ page = method(*args, **kwargs)
70
+ results.extend(page.results)
71
+ return PagingResponse(
72
+ previous=None,
73
+ next=None,
74
+ count=len(results),
75
+ results=results,
76
+ )
77
+
78
+ return wrapper
79
+
80
+
81
+ class BaseNetboxClient(RequestsClient):
82
+ def __init__(self, url: str, token: str):
83
+ url = url.rstrip("/") + "/api/"
84
+ session = Session()
85
+ if token:
86
+ session.headers["Authorization"] = f"Token {token}"
87
+ super().__init__(url, session)
@@ -0,0 +1,62 @@
1
+ from logging import getLogger
2
+
3
+ from annet.annlib.netdev.views.hardware import HardwareView
4
+
5
+ logger = getLogger(__name__)
6
+
7
+ _VENDORS = {
8
+ "cisco": "Cisco",
9
+ "catalyst": "Cisco Catalyst",
10
+ "nexus": "Cisco Nexus",
11
+ "huawei": "Huawei",
12
+ "juniper": "Juniper",
13
+ "arista": "Arista",
14
+ "pc": "PC",
15
+ "nokia": "Nokia",
16
+ "aruba": "Aruba",
17
+ "routeros": "RouterOS",
18
+ "ribbon": "Ribbon",
19
+ }
20
+
21
+
22
+ def _vendor_to_hw(vendor):
23
+ return HardwareView(_VENDORS.get(vendor.lower(), vendor), None)
24
+
25
+
26
+ def get_hw(manufacturer: str, model: str):
27
+ # by some reason Netbox calls Mellanox SN as MSN, so we fix them here
28
+ if manufacturer == "Mellanox" and model.startswith("MSN"):
29
+ model = model.replace("MSN", "SN", 1)
30
+ hw = _vendor_to_hw(manufacturer + " " + model)
31
+ if not hw:
32
+ raise ValueError(f"unsupported manufacturer {manufacturer}")
33
+ return hw
34
+
35
+
36
+ def get_breed(manufacturer: str, model: str):
37
+ if manufacturer == "Huawei" and model.startswith("CE"):
38
+ return "vrp85"
39
+ elif manufacturer == "Huawei" and model.startswith("NE"):
40
+ return "vrp85"
41
+ elif manufacturer == "Huawei":
42
+ return "vrp55"
43
+ elif manufacturer == "Mellanox":
44
+ return "cuml2"
45
+ elif manufacturer == "Juniper":
46
+ return "jun10"
47
+ elif manufacturer == "Cisco":
48
+ return "ios12"
49
+ elif manufacturer == "Adva":
50
+ return "adva8"
51
+ elif manufacturer == "Arista":
52
+ return "eos4"
53
+ raise ValueError(f"unsupported manufacturer {manufacturer}")
54
+
55
+
56
+ def is_supported(manufacturer: str) -> bool:
57
+ if manufacturer not in (
58
+ "Huawei", "Mellanox", "Juniper", "Cisco", "Adva", "Arista",
59
+ ):
60
+ logger.warning("Unsupported manufacturer `%s`", manufacturer)
61
+ return False
62
+ return True
@@ -0,0 +1,105 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from typing import List, Optional, Any, Dict
4
+
5
+ from annet.annlib.netdev.views.hardware import HardwareView
6
+ from annet.storage import Storage
7
+
8
+
9
+ @dataclass
10
+ class Entity:
11
+ id: int
12
+ name: str
13
+
14
+
15
+ @dataclass
16
+ class Label:
17
+ value: str
18
+ label: str
19
+
20
+
21
+ @dataclass
22
+ class IpFamily:
23
+ value: int
24
+ label: str
25
+
26
+
27
+ @dataclass
28
+ class DeviceType:
29
+ id: int
30
+ manufacturer: Entity
31
+ model: str
32
+
33
+
34
+ @dataclass
35
+ class DeviceIp:
36
+ id: int
37
+ display: str
38
+ address: str
39
+ family: int
40
+
41
+
42
+ @dataclass
43
+ class IpAddress:
44
+ id: int
45
+ assigned_object_id: int
46
+ display: str
47
+ family: IpFamily
48
+ address: str
49
+ status: Label
50
+ tags: List[Entity]
51
+ created: datetime
52
+ last_updated: datetime
53
+
54
+
55
+ @dataclass
56
+ class Interface(Entity):
57
+ device: Entity
58
+ enabled: bool
59
+ display: str = ""
60
+ ip_addresses: List[IpAddress] = field(default_factory=list)
61
+
62
+
63
+ @dataclass
64
+ class NetboxDevice(Entity):
65
+ url: str
66
+ storage: Storage
67
+ neighbours_ids: List[int]
68
+
69
+ display: str
70
+ device_type: DeviceType
71
+ device_role: Entity
72
+ tenant: Optional[Entity]
73
+ platform: Optional[Entity]
74
+ serial: str
75
+ asset_tag: Optional[str]
76
+ site: Entity
77
+ rack: Optional[Entity]
78
+ position: Optional[float]
79
+ face: Optional[Label]
80
+ status: Label
81
+ primary_ip: Optional[DeviceIp]
82
+ primary_ip4: Optional[DeviceIp]
83
+ primary_ip6: Optional[DeviceIp]
84
+ tags: List[Entity]
85
+ custom_fields: Dict[str, Any]
86
+ created: datetime
87
+ last_updated: datetime
88
+
89
+ fqdn: str
90
+ hostname: str
91
+ hw: Optional[HardwareView]
92
+ breed: str
93
+
94
+ interfaces: List[Interface]
95
+
96
+ # compat
97
+
98
+ def __hash__(self):
99
+ return hash((self.id, type(self)))
100
+
101
+ def __eq__(self, other):
102
+ return type(self) is type(other) and self.url == other.url
103
+
104
+ def is_pc(self):
105
+ return self.device_type.manufacturer.name == "Mellanox"
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Union, Iterable, Optional
3
+
4
+ from annet.storage import Query
5
+
6
+
7
+ @dataclass
8
+ class NetboxQuery(Query):
9
+ query: List[str]
10
+
11
+ @classmethod
12
+ def new(
13
+ cls, query: Union[str, Iterable[str]],
14
+ hosts_range: Optional[slice] = None,
15
+ ) -> "NetboxQuery":
16
+ if hosts_range is not None:
17
+ raise ValueError("host_range is not supported")
18
+ return cls(query=list(query))
19
+
20
+ @property
21
+ def globs(self):
22
+ # We process every query host as a glob
23
+ return self.query
@@ -0,0 +1,25 @@
1
+ from dataclasses import dataclass
2
+ from typing import Dict
3
+
4
+ from adaptix import Retort, name_mapping, NameStyle
5
+ from dataclass_rest import get
6
+ from dataclass_rest.client_protocol import FactoryProtocol
7
+
8
+ from .client import BaseNetboxClient
9
+
10
+
11
+ @dataclass
12
+ class Status:
13
+ netbox_version: str
14
+ plugins: Dict[str, str]
15
+
16
+
17
+ class NetboxStatusClient(BaseNetboxClient):
18
+ def _init_response_body_factory(self) -> FactoryProtocol:
19
+ return Retort(recipe=[
20
+ name_mapping(name_style=NameStyle.LOWER_KEBAB)
21
+ ])
22
+
23
+ @get("status")
24
+ def status(self) -> Status:
25
+ ...
@@ -0,0 +1,14 @@
1
+ import os
2
+
3
+
4
+ class NetboxStorageOpts:
5
+ def __init__(self, url: str, token: str):
6
+ self.url = url
7
+ self.token = token
8
+
9
+ @classmethod
10
+ def from_cli_opts(cls, cli_opts):
11
+ return cls(
12
+ url=os.getenv("NETBOX_URL", "http://localhost"),
13
+ token=os.getenv("NETBOX_TOKEN", "").strip(),
14
+ )
@@ -0,0 +1,34 @@
1
+ from dataclass_rest.exceptions import ClientError
2
+
3
+ from annet.storage import StorageProvider, Storage
4
+ from .common.status_client import NetboxStatusClient
5
+ from .common.storage_opts import NetboxStorageOpts
6
+ from .common.query import NetboxQuery
7
+ from .v24.storage import NetboxStorageV24
8
+ from .v37.storage import NetboxStorageV37
9
+
10
+
11
+ def storage_factory(opts: NetboxStorageOpts) -> Storage:
12
+ client = NetboxStatusClient(opts.url, opts.token)
13
+ try:
14
+ status = client.status()
15
+ except ClientError as e:
16
+ if e.status_code == 404:
17
+ # old version do not support status reqeust
18
+ return NetboxStorageV24(opts)
19
+ raise
20
+ if status.netbox_version.startswith("3."):
21
+ return NetboxStorageV37(opts)
22
+ else:
23
+ raise ValueError(f"Unsupported version: {status.netbox_version}")
24
+
25
+
26
+ class NetboxProvider(StorageProvider):
27
+ def storage(self):
28
+ return storage_factory
29
+
30
+ def opts(self):
31
+ return NetboxStorageOpts
32
+
33
+ def query(self):
34
+ return NetboxQuery
File without changes
@@ -0,0 +1,73 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import List, Optional, Any, Dict
4
+
5
+ from annet.adapters.netbox.common.models import Entity, DeviceType
6
+
7
+
8
+ @dataclass
9
+ class Label:
10
+ value: int
11
+ label: str
12
+
13
+
14
+ @dataclass
15
+ class DeviceIp:
16
+ id: int
17
+ address: str
18
+ family: int
19
+
20
+
21
+ @dataclass
22
+ class Device(Entity):
23
+ url: str
24
+ display_name: str
25
+ device_type: DeviceType
26
+ device_role: Entity
27
+ tenant: Optional[Entity]
28
+ platform: Optional[Entity]
29
+ serial: str
30
+ asset_tag: Optional[str]
31
+ site: Entity
32
+ rack: Optional[Entity]
33
+ position: Optional[float]
34
+ face: Optional[Label]
35
+ status: Label
36
+ primary_ip: Optional[DeviceIp]
37
+ primary_ip4: Optional[DeviceIp]
38
+ primary_ip6: Optional[DeviceIp]
39
+ tags: List[str]
40
+ custom_fields: Dict[str, Any]
41
+ created: datetime
42
+ last_updated: datetime
43
+
44
+
45
+ @dataclass
46
+ class Interface(Entity):
47
+ device: Entity
48
+ enabled: bool
49
+
50
+
51
+ @dataclass
52
+ class Vrf(Entity):
53
+ rd: str
54
+
55
+
56
+ @dataclass
57
+ class IpAddress:
58
+ id: int
59
+ family: int
60
+ address: str
61
+ vrf: Optional[Vrf]
62
+ tenant: Any # ???
63
+ status: Label
64
+ description: Optional[str]
65
+ custom_fields: Dict[str, Any]
66
+ tags: List[str]
67
+ created: datetime
68
+ last_updated: datetime
69
+
70
+ interface: Entity
71
+
72
+ nat_inside: Any # ???
73
+ nat_outside: Any # ???
@@ -0,0 +1,59 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ import dateutil.parser
5
+ from adaptix import Retort, loader
6
+ from dataclass_rest import get
7
+
8
+ from annet.adapters.netbox.common.client import (
9
+ BaseNetboxClient, collect, PagingResponse,
10
+ )
11
+ from .api_models import Device, Interface, IpAddress
12
+
13
+
14
+ class NetboxV24(BaseNetboxClient):
15
+ def _init_response_body_factory(self) -> Retort:
16
+ return Retort(recipe=[
17
+ loader(datetime, dateutil.parser.parse)
18
+ ])
19
+
20
+ @get("dcim/interfaces")
21
+ def interfaces(
22
+ self,
23
+ device_id: Optional[List[int]] = None,
24
+ limit: int = 20,
25
+ offset: int = 0,
26
+ ) -> PagingResponse[Interface]:
27
+ pass
28
+
29
+ all_interfaces = collect(interfaces, field="device_id")
30
+
31
+ @get("ipam/ip-addresses")
32
+ def ip_addresses(
33
+ self,
34
+ interface_id: Optional[List[int]] = None,
35
+ limit: int = 20,
36
+ offset: int = 0,
37
+ ) -> PagingResponse[IpAddress]:
38
+ pass
39
+
40
+ all_ip_addresses = collect(ip_addresses, field="interface_id")
41
+
42
+ @get("dcim/devices")
43
+ def devices(
44
+ self,
45
+ name: Optional[List[str]] = None,
46
+ tag: Optional[str] = None,
47
+ limit: int = 20,
48
+ offset: int = 0,
49
+ ) -> PagingResponse[Device]:
50
+ pass
51
+
52
+ all_devices = collect(devices)
53
+
54
+ @get("dcim/devices/{device_id}")
55
+ def get_device(
56
+ self,
57
+ device_id: int,
58
+ ) -> Device:
59
+ pass