nac-test-pyats-common 0.1.0__py3-none-any.whl → 0.2.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.
@@ -1,8 +1,12 @@
1
+ # SPDX-License-Identifier: MPL-2.0
2
+ # Copyright (c) 2025 Daniel Schmidt
3
+
1
4
  """SDWAN Manager authentication implementation for Cisco SD-WAN.
2
5
 
3
6
  This module provides authentication functionality for Cisco SDWAN Manager (formerly
4
7
  vManage), which manages the software-defined WAN fabric. The authentication mechanism
5
- uses form-based login with JSESSIONID cookie and optional XSRF token for CSRF protection.
8
+ uses form-based login with JSESSIONID cookie and optional XSRF token for CSRF
9
+ protection.
6
10
 
7
11
  The module implements a two-tier API design:
8
12
  1. _authenticate() - Low-level method that performs direct SDWAN Manager authentication
@@ -16,7 +20,9 @@ import os
16
20
  from typing import Any
17
21
 
18
22
  import httpx
19
- from nac_test.pyats_core.common.auth_cache import AuthCache # type: ignore[import-untyped]
23
+ from nac_test.pyats_core.common.auth_cache import (
24
+ AuthCache, # type: ignore[import-untyped]
25
+ )
20
26
 
21
27
  # Default session lifetime for SDWAN Manager authentication in seconds
22
28
  # SDWAN Manager sessions are typically valid for 30 minutes (1800 seconds) by default
@@ -56,7 +62,9 @@ class SDWANManagerAuth:
56
62
  """
57
63
 
58
64
  @staticmethod
59
- def _authenticate(url: str, username: str, password: str) -> tuple[dict[str, Any], int]:
65
+ def _authenticate(
66
+ url: str, username: str, password: str
67
+ ) -> tuple[dict[str, Any], int]:
60
68
  """Perform direct SDWAN Manager authentication and obtain session data.
61
69
 
62
70
  This method performs a direct authentication request to the SDWAN Manager
@@ -149,8 +157,9 @@ class SDWANManagerAuth:
149
157
  expired ones. This significantly reduces the number of authentication
150
158
  requests to the SDWAN Manager.
151
159
 
152
- The method uses a cache key based on the controller type ("SDWAN_MANAGER") and
153
- URL to ensure proper session isolation between different SDWAN Manager instances.
160
+ The method uses a cache key based on the controller type ("SDWAN_MANAGER")
161
+ and URL to ensure proper session isolation between different SDWAN Manager
162
+ instances.
154
163
 
155
164
  Environment Variables Required:
156
165
  SDWAN_URL: Base URL of the SDWAN Manager
@@ -197,7 +206,9 @@ class SDWANManagerAuth:
197
206
  missing_vars.append("SDWAN_USERNAME")
198
207
  if not password:
199
208
  missing_vars.append("SDWAN_PASSWORD")
200
- raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
209
+ raise ValueError(
210
+ f"Missing required environment variables: {', '.join(missing_vars)}"
211
+ )
201
212
 
202
213
  # Normalize URL by removing trailing slash
203
214
  url = url.rstrip("/") # type: ignore[union-attr]
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MPL-2.0
2
+ # Copyright (c) 2025 Daniel Schmidt
3
+
1
4
  """SD-WAN-specific device resolver for parsing the NAC data model.
2
5
 
3
6
  This module provides the SDWANDeviceResolver class, which extends
@@ -22,13 +25,20 @@ class SDWANDeviceResolver(BaseDeviceResolver):
22
25
 
23
26
  Schema structure:
24
27
  sdwan:
28
+ management_ip_variable: "vpn511_int1_if_ipv4_address" # Global default
25
29
  sites:
26
30
  - name: "site1"
27
31
  routers:
28
32
  - chassis_id: "abc123"
33
+ management_ip_variable: "custom_mgmt_ip" # Router override
29
34
  device_variables:
30
35
  system_hostname: "router1"
31
- vpn10_mgmt_ip: "10.1.1.100/32"
36
+ vpn511_int1_if_ipv4_address: "10.1.1.100/32"
37
+ custom_mgmt_ip: "10.2.2.200/32"
38
+
39
+ Management IP Resolution Priority:
40
+ 1. Router-level management_ip_variable (highest priority)
41
+ 2. Global sdwan-level management_ip_variable (fallback)
32
42
 
33
43
  Credentials:
34
44
  Uses IOSXE_USERNAME and IOSXE_PASSWORD environment variables
@@ -53,7 +63,7 @@ class SDWANDeviceResolver(BaseDeviceResolver):
53
63
  """Return 'sdwan' as the root key in the data model.
54
64
 
55
65
  Returns:
56
- Root key used when loading test inventory and navigating schema.
66
+ Root key used when navigating the schema.
57
67
  """
58
68
  return "sdwan"
59
69
 
@@ -111,7 +121,9 @@ class SDWANDeviceResolver(BaseDeviceResolver):
111
121
 
112
122
  # Fallback to chassis_id
113
123
  chassis_id = device_data.get("chassis_id", "unknown")
114
- logger.warning(f"No system_hostname found for {chassis_id}, using chassis_id as hostname")
124
+ logger.warning(
125
+ f"No system_hostname found for {chassis_id}, using chassis_id as hostname"
126
+ )
115
127
  return str(chassis_id)
116
128
 
117
129
  def extract_host_ip(self, device_data: dict[str, Any]) -> str:
@@ -121,6 +133,10 @@ class SDWANDeviceResolver(BaseDeviceResolver):
121
133
  Uses management_ip_variable field to determine which variable
122
134
  contains the management IP.
123
135
 
136
+ Resolution priority:
137
+ 1. Router-level management_ip_variable (highest priority)
138
+ 2. Global sdwan-level management_ip_variable (fallback)
139
+
124
140
  Args:
125
141
  device_data: Router data dictionary from the data model.
126
142
 
@@ -128,27 +144,28 @@ class SDWANDeviceResolver(BaseDeviceResolver):
128
144
  IP address string without CIDR notation (e.g., "10.1.1.100").
129
145
 
130
146
  Raises:
131
- ValueError: If no management IP can be found.
147
+ ValueError: If management_ip_variable is not configured or
148
+ the referenced variable is not found in device_variables.
132
149
  """
133
150
  device_vars = device_data.get("device_variables", {})
134
151
 
135
- # Get the variable name that contains the management IP
152
+ # Cascading lookup: router-level > global sdwan-level
136
153
  ip_var = device_data.get("management_ip_variable")
154
+ if not ip_var:
155
+ ip_var = self.data_model.get("sdwan", {}).get("management_ip_variable")
156
+
157
+ if not ip_var:
158
+ raise ValueError(
159
+ "management_ip_variable not configured. "
160
+ "Set it at router level or sdwan level in sites.nac.yaml."
161
+ )
137
162
 
138
- if ip_var and ip_var in device_vars:
139
- ip_value = str(device_vars[ip_var])
140
- else:
141
- # Fallback: try common variable names
142
- for fallback_var in ["mgmt_ip", "management_ip", "vpn0_ip"]:
143
- if fallback_var in device_vars:
144
- ip_value = str(device_vars[fallback_var])
145
- break
146
- else:
147
- raise ValueError(
148
- "Could not find management IP for device. "
149
- "Set 'management_ip_variable' in test_inventory or use "
150
- "standard variable names (mgmt_ip, management_ip, vpn0_ip)."
151
- )
163
+ if ip_var not in device_vars:
164
+ raise ValueError(
165
+ f"management_ip_variable '{ip_var}' not found in device_variables."
166
+ )
167
+
168
+ ip_value = str(device_vars[ip_var])
152
169
 
153
170
  # Strip CIDR notation if present
154
171
  if "/" in ip_value:
@@ -157,15 +174,35 @@ class SDWANDeviceResolver(BaseDeviceResolver):
157
174
  return ip_value
158
175
 
159
176
  def extract_os_type(self, device_data: dict[str, Any]) -> str:
160
- """Extract OS type, defaulting to 'iosxe' for SD-WAN edges.
177
+ """Return 'iosxe' as all SD-WAN edge devices are IOS-XE based.
178
+
179
+ Args:
180
+ device_data: Router data dictionary (unused, OS is hardcoded).
181
+
182
+ Returns:
183
+ Always returns 'iosxe'.
184
+ """
185
+ return "iosxe"
186
+
187
+ def build_device_dict(self, device_data: dict[str, Any]) -> dict[str, Any]:
188
+ """Build device dictionary with SD-WAN specific defaults.
189
+
190
+ Extends the base implementation to add type='router' since
191
+ all SD-WAN edge devices are routers.
161
192
 
162
193
  Args:
163
194
  device_data: Router data dictionary from the data model.
164
195
 
165
196
  Returns:
166
- OS type string (e.g., "iosxe", "nxos", "iosxr").
197
+ Device dictionary with hostname, host, os, device_id, and type.
167
198
  """
168
- return str(device_data.get("os", "iosxe"))
199
+ # Get base device dict from parent
200
+ device_dict = super().build_device_dict(device_data)
201
+
202
+ # Add type - all SD-WAN edges are routers
203
+ device_dict["type"] = "router"
204
+
205
+ return device_dict
169
206
 
170
207
  def get_credential_env_vars(self) -> tuple[str, str]:
171
208
  """Return IOS-XE credential env vars for SD-WAN edge devices.
@@ -1,3 +1,6 @@
1
+ # SPDX-License-Identifier: MPL-2.0
2
+ # Copyright (c) 2025 Daniel Schmidt
3
+
1
4
  """SD-WAN specific base test class for SSH/Direct-to-Device testing.
2
5
 
3
6
  This module provides the SDWANTestBase class, which extends the generic SSHTestBase
@@ -17,7 +20,9 @@ import logging
17
20
  import os
18
21
  from typing import Any
19
22
 
20
- from nac_test.pyats_core.common.ssh_base_test import SSHTestBase # type: ignore[import-untyped]
23
+ from nac_test.pyats_core.common.ssh_base_test import (
24
+ SSHTestBase, # type: ignore[import-untyped]
25
+ )
21
26
 
22
27
  from .device_resolver import SDWANDeviceResolver
23
28
 
@@ -43,18 +48,27 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
43
48
  pass
44
49
  """
45
50
 
51
+ # Class-level storage for the last resolver instance
52
+ # This allows nac-test to access skipped_devices after calling
53
+ # get_ssh_device_inventory()
54
+ _last_resolver: "SDWANDeviceResolver | None" = None
55
+
46
56
  @classmethod
47
- def get_ssh_device_inventory(cls, data_model: dict[str, Any]) -> list[dict[str, Any]]:
57
+ def get_ssh_device_inventory(
58
+ cls, data_model: dict[str, Any]
59
+ ) -> list[dict[str, Any]]:
48
60
  """Parse the SD-WAN data model to retrieve the device inventory.
49
61
 
50
62
  This method is the entry point called by nac-test's orchestrator.
51
63
  All device inventory resolution is delegated to SDWANDeviceResolver,
52
64
  which handles:
53
- - Test inventory loading (test_inventory.yaml)
54
65
  - Schema navigation (sites[].routers[])
55
- - Variable resolution (hostnames, IPs)
66
+ - Management IP resolution via management_ip_variable
56
67
  - Credential injection (IOSXE_USERNAME, IOSXE_PASSWORD)
57
68
 
69
+ After calling this method, access cls._last_resolver.skipped_devices
70
+ to get information about devices that failed resolution.
71
+
58
72
  Args:
59
73
  data_model: The merged data model from nac-test containing all
60
74
  sites.nac.yaml data with resolved variables.
@@ -73,13 +87,8 @@ class SDWANTestBase(SSHTestBase): # type: ignore[misc]
73
87
  """
74
88
  logger.info("SDWANTestBase: Resolving device inventory via SDWANDeviceResolver")
75
89
 
76
- # Delegate entirely to the resolver
77
- # SDWANDeviceResolver handles:
78
- # 1. Test inventory loading via BaseDeviceResolver._load_inventory()
79
- # 2. Schema navigation via navigate_to_devices()
80
- # 3. Credential injection via _inject_credentials() using IOSXE_* env vars
81
- resolver = SDWANDeviceResolver(data_model)
82
- return resolver.get_resolved_inventory()
90
+ cls._last_resolver = SDWANDeviceResolver(data_model)
91
+ return cls._last_resolver.get_resolved_inventory()
83
92
 
84
93
  def get_device_credentials(self, device: dict[str, Any]) -> dict[str, str | None]:
85
94
  """Get SD-WAN edge device SSH credentials from environment variables.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nac-test-pyats-common
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Architecture adapters for Network as Code (NaC) PyATS testing - auth classes, test base classes, and device resolvers
5
5
  Project-URL: Homepage, https://github.com/netascode/nac-test-pyats-common
6
6
  Project-URL: Documentation, https://github.com/netascode/nac-test-pyats-common
@@ -24,6 +24,7 @@ Classifier: Topic :: Software Development :: Testing
24
24
  Classifier: Topic :: System :: Networking
25
25
  Requires-Python: >=3.10
26
26
  Requires-Dist: httpx>=0.28
27
+ Requires-Dist: nac-test==1.1.0b2
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: bandit[toml]>=1.8.6; extra == 'dev'
29
30
  Requires-Dist: mypy>=1.10; extra == 'dev'
@@ -0,0 +1,25 @@
1
+ nac_test_pyats_common/__init__.py,sha256=4fOaRhzkJJDyW00-wPzEzgWdmLXSLfxkiV-Z39RWmq8,1770
2
+ nac_test_pyats_common/py.typed,sha256=BrP39il8_cNN1K0KDeyBUtySS9oI2_SK5xKx1SxdSoY,59
3
+ nac_test_pyats_common/aci/__init__.py,sha256=Y9VhBZ3_GXLBk3_Wtg1SRG_Dxx134Etf9k-0wvrOMwQ,1335
4
+ nac_test_pyats_common/aci/auth.py,sha256=1Rk2uBGb_CGgkBh9dD3LagIsbJPKRgsSFDYEeyiC50U,7867
5
+ nac_test_pyats_common/aci/test_base.py,sha256=SoPh91AXPvMshJXIyjiZumSgxOAN4xPkpSb3t0B9kdE,6256
6
+ nac_test_pyats_common/catc/__init__.py,sha256=ZGOQBvjFvq7h9LtfOyxFEmlir7htpy8sfWoJuDwGjsA,1986
7
+ nac_test_pyats_common/catc/api_test_base.py,sha256=kGp6rZ4S3zP3MW5BD02K5uVxmtE-vRlG2A99kaLgyDo,7507
8
+ nac_test_pyats_common/catc/auth.py,sha256=1oDZr_PEWOz1IZOUm_kyKzIFPQubMo_wemj-Q3D7ALU,9522
9
+ nac_test_pyats_common/catc/device_resolver.py,sha256=YgJJn1pXONXCAMnfXA-_Iuo6tkLtD9EekHbPbae6mRg,5363
10
+ nac_test_pyats_common/catc/ssh_test_base.py,sha256=wUldp2T9cmSEALBtbOgUQvQ2txuid3ErJEO27kM-kiI,4482
11
+ nac_test_pyats_common/common/__init__.py,sha256=Y0qrZEKx2g8Zm1CTHJpeLocwMasspP6NQuhyrRiA1wA,1084
12
+ nac_test_pyats_common/common/base_device_resolver.py,sha256=xP4QF-kNp_8rFo9N6wzfJSIpNOgnPkeZmWAbH0k5tGo,15091
13
+ nac_test_pyats_common/iosxe/__init__.py,sha256=GSuLGyxcu8dFouspTjXuQ1Mi27vDluVR8ZCcJrPcIiY,2147
14
+ nac_test_pyats_common/iosxe/iosxe_resolver.py,sha256=XzzwRCa1cuQ6HmeUB1Z2vITOwYdhuDlugDD1eVXAoIE,2355
15
+ nac_test_pyats_common/iosxe/registry.py,sha256=djRFzxuEu8IdyTmi1puhIHGZGSMjys0l1v4-uzYCwAU,5723
16
+ nac_test_pyats_common/iosxe/test_base.py,sha256=NjXbf0ZY-CBWhd3jpIAhylL5kyRrHM6FlhGlFtjLSjM,4569
17
+ nac_test_pyats_common/sdwan/__init__.py,sha256=mrziVp5kMgykKnFPvWykuVLuky6xwEv6oEcfrbxFXxY,1777
18
+ nac_test_pyats_common/sdwan/api_test_base.py,sha256=NuqrxODrRw7ZvEIuoc1Bup60thKqJOfiGz96T4QJ8Ng,7632
19
+ nac_test_pyats_common/sdwan/auth.py,sha256=CMgf_AYCalVQNjdUfDlE-0-z5tnhU_Wnscgt5ZW61RY,10322
20
+ nac_test_pyats_common/sdwan/device_resolver.py,sha256=1GdM5QKftj4F5bGmsbH90--VqoFZaDPMerHF1dAceMQ,7169
21
+ nac_test_pyats_common/sdwan/ssh_test_base.py,sha256=rZF7txhu0FGtPtTzGMZFqB9hwClmtggroZwRuX3BGU4,4210
22
+ nac_test_pyats_common-0.2.0.dist-info/METADATA,sha256=9Qbd0AYAGd33wI1iwbynSClOmApEC-Iny0geUrBoIzA,13072
23
+ nac_test_pyats_common-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
+ nac_test_pyats_common-0.2.0.dist-info/licenses/LICENSE,sha256=zt2sx-c0iEk6-OO0iqRQ4l6fIGazRKW_qLMqfDpLm6M,16295
25
+ nac_test_pyats_common-0.2.0.dist-info/RECORD,,
@@ -1,64 +0,0 @@
1
- """Catalyst Center device resolver placeholder for D2D testing.
2
-
3
- This is a placeholder for the Catalyst Center resolver that will be implemented
4
- when Catalyst Center D2D testing support is added. This resolver will handle
5
- IOS-XE devices managed by Catalyst Center.
6
-
7
- Expected environment variables:
8
- - CC_URL: Catalyst Center controller URL
9
- - CC_USERNAME: Catalyst Center username
10
- - CC_PASSWORD: Catalyst Center password
11
- - IOSXE_USERNAME: SSH username for devices
12
- - IOSXE_PASSWORD: SSH password for devices
13
-
14
- """
15
-
16
- from typing import Any
17
-
18
- from nac_test_pyats_common.common import BaseDeviceResolver
19
-
20
- from .registry import register_iosxe_resolver
21
-
22
-
23
- @register_iosxe_resolver("CC")
24
- class CatalystCenterDeviceResolver(BaseDeviceResolver):
25
- """Placeholder resolver for Catalyst Center D2D testing.
26
-
27
- This resolver will be implemented when Catalyst Center D2D testing
28
- support is added. Currently a placeholder to reserve the CC registry slot.
29
- """
30
-
31
- def get_architecture_name(self) -> str:
32
- """Return architecture name."""
33
- return "catalyst_center"
34
-
35
- def get_schema_root_key(self) -> str:
36
- """Return data model root key."""
37
- return "catalyst_center"
38
-
39
- def navigate_to_devices(self) -> list[dict[str, Any]]:
40
- """Navigate to devices in data model."""
41
- raise NotImplementedError(
42
- "CatalystCenterDeviceResolver is not yet implemented. "
43
- "This placeholder reserves the CC registry slot for future use."
44
- )
45
-
46
- def extract_device_id(self, device_data: dict[str, Any]) -> str:
47
- """Extract device ID."""
48
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
49
-
50
- def extract_hostname(self, device_data: dict[str, Any]) -> str:
51
- """Extract hostname."""
52
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
53
-
54
- def extract_host_ip(self, device_data: dict[str, Any]) -> str:
55
- """Extract management IP."""
56
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
57
-
58
- def extract_os_type(self, device_data: dict[str, Any]) -> str:
59
- """Extract OS type."""
60
- raise NotImplementedError("CatalystCenterDeviceResolver is not yet implemented")
61
-
62
- def get_credential_env_vars(self) -> tuple[str, str]:
63
- """Return credential environment variable names."""
64
- return ("IOSXE_USERNAME", "IOSXE_PASSWORD")
@@ -1,24 +0,0 @@
1
- nac_test_pyats_common/__init__.py,sha256=yhvIGEkgMoZhwZKWxXUTzkzQT6-6YtpdOOa8mAVgNaY,1551
2
- nac_test_pyats_common/py.typed,sha256=BrP39il8_cNN1K0KDeyBUtySS9oI2_SK5xKx1SxdSoY,59
3
- nac_test_pyats_common/aci/__init__.py,sha256=hEqGdhP_DtCqmLnUncYdmmH5d5WFRsdIkoCzH3DbV1k,1263
4
- nac_test_pyats_common/aci/auth.py,sha256=CSBHmmN8GYNXP-uCC6u31S-AOWcUDhIeLwuolN4OEjU,7772
5
- nac_test_pyats_common/aci/test_base.py,sha256=Em0GTrU0lJLWhwoZOptwnEEDnY3L8YkB-2Sre4endzI,6131
6
- nac_test_pyats_common/catc/__init__.py,sha256=1e5RjeEKApalS9CqSRi_nvER1MRtaCfQJ-kVOq8zp0w,1247
7
- nac_test_pyats_common/catc/auth.py,sha256=yCp9NGe48CngxGN0QVLS17ukfJge7rdBcV-L2_lez7U,9350
8
- nac_test_pyats_common/catc/test_base.py,sha256=myhdMFN310z6D3IC_BcSvtNQ4iyGbZZtiROxoNAHs0I,7367
9
- nac_test_pyats_common/common/__init__.py,sha256=tMBObh27Bl3AHe4UVu3EoI6rbhxWV901WKw4SEMvnwk,1012
10
- nac_test_pyats_common/common/base_device_resolver.py,sha256=VQDkV6EzZCJL9TrJcB2i34BFz9Y8cK7g2Y_8NP7AVrQ,18420
11
- nac_test_pyats_common/iosxe/__init__.py,sha256=dIXPUdOJAy8P7KM4_scfkFSB4g1c-b7AdsEC3Ok8gEE,2075
12
- nac_test_pyats_common/iosxe/catc_resolver.py,sha256=YK8-1Y0EIkX5CBawAqAxYk2kMjYwoBWXLtM3Yz_7kfQ,2441
13
- nac_test_pyats_common/iosxe/iosxe_resolver.py,sha256=CmvbF8eFo5I_Uy_JDx1lO-2YaB_fiDxK-I7WBvu7X5o,2283
14
- nac_test_pyats_common/iosxe/registry.py,sha256=toRNcIcXkPPrm-kC4tpxOf6kTXgvC2Dsh8Vm5EXEwRY,5562
15
- nac_test_pyats_common/iosxe/test_base.py,sha256=dJrTpRy3V8Q0vK5FODHzDh85EZ7eM8dS9PnBvRF-i_0,4220
16
- nac_test_pyats_common/sdwan/__init__.py,sha256=QUnYDV25cwubonnpXkkhGrDcSOcDtmabAAx8fPnM4zQ,1697
17
- nac_test_pyats_common/sdwan/api_test_base.py,sha256=gTy57JSa6pjuCayfZMzS1lUPZHYSTqZjr8LeDNoLouc,7507
18
- nac_test_pyats_common/sdwan/auth.py,sha256=V_OVAZzCSOvfTYrPGyv-xXZEDDHIpTl2BBxDoB5Mwro,10189
19
- nac_test_pyats_common/sdwan/device_resolver.py,sha256=huAfUG6TADF3lWeOUyr244jRogC5SqWhz4k-1191ZnY,5968
20
- nac_test_pyats_common/sdwan/ssh_test_base.py,sha256=KvV_Hw56EiX5HcVRNG2A2ryLH0v6RbmLlUzyJMQ-424,4078
21
- nac_test_pyats_common-0.1.0.dist-info/METADATA,sha256=hZNaqHtElHIADhf2CumhWpg8qf9aEWEE_eM2tIr6IQw,13039
22
- nac_test_pyats_common-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
- nac_test_pyats_common-0.1.0.dist-info/licenses/LICENSE,sha256=zt2sx-c0iEk6-OO0iqRQ4l6fIGazRKW_qLMqfDpLm6M,16295
24
- nac_test_pyats_common-0.1.0.dist-info/RECORD,,