nac-test-pyats-common 0.1.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.
@@ -0,0 +1,49 @@
1
+ """NAC PyATS Common - Architecture adapters for NAC PyATS testing.
2
+
3
+ This package provides architecture-specific authentication, test base classes,
4
+ and device resolver implementations for use with the nac-test framework. It
5
+ consolidates duplicated PyATS testing infrastructure from multiple NAC
6
+ architecture repositories (ACI, SD-WAN, Catalyst Center) into a single,
7
+ centralized, and maintainable package.
8
+
9
+ Supported Architectures:
10
+ - ACI (APIC): nac_test_pyats_common.aci
11
+ - SD-WAN (SDWAN Manager): nac_test_pyats_common.sdwan
12
+ - Catalyst Center: nac_test_pyats_common.catc
13
+
14
+ Example:
15
+ >>> # For ACI/APIC testing
16
+ >>> from nac_test_pyats_common.aci import APICTestBase
17
+ >>>
18
+ >>> # For SD-WAN/SDWAN Manager testing
19
+ >>> from nac_test_pyats_common.sdwan import SDWANManagerTestBase, SDWANTestBase
20
+ >>>
21
+ >>> # For Catalyst Center testing
22
+ >>> from nac_test_pyats_common.catc import CatalystCenterTestBase
23
+ """
24
+
25
+ __version__ = "1.0.0"
26
+
27
+ # Public API - Import from subpackages
28
+ from nac_test_pyats_common.aci import APICAuth, APICTestBase
29
+ from nac_test_pyats_common.catc import CatalystCenterAuth, CatalystCenterTestBase
30
+ from nac_test_pyats_common.sdwan import (
31
+ SDWANDeviceResolver,
32
+ SDWANManagerAuth,
33
+ SDWANManagerTestBase,
34
+ SDWANTestBase,
35
+ )
36
+
37
+ __all__ = [
38
+ # ACI/APIC
39
+ "APICAuth",
40
+ "APICTestBase",
41
+ # SD-WAN/SDWAN Manager
42
+ "SDWANManagerAuth",
43
+ "SDWANManagerTestBase",
44
+ "SDWANTestBase",
45
+ "SDWANDeviceResolver",
46
+ # Catalyst Center
47
+ "CatalystCenterAuth",
48
+ "CatalystCenterTestBase",
49
+ ]
@@ -0,0 +1,40 @@
1
+ """ACI adapter module for PyATS common utilities.
2
+
3
+ This module provides the core ACI-specific components for PyATS testing, including
4
+ authentication handling and base test classes. It serves as the primary interface
5
+ for interacting with Cisco ACI (Application Centric Infrastructure) environments
6
+ within the PyATS testing framework.
7
+
8
+ The module exports two primary components:
9
+ 1. APICAuth: Handles authentication and session management for APIC controllers
10
+ 2. APICTestBase: Provides the base class for all ACI-specific PyATS tests
11
+
12
+ Example:
13
+ Basic usage of the ACI module components:
14
+
15
+ ```python
16
+ from nac_test_pyats_common.aci import APICAuth, APICTestBase
17
+
18
+ # Create authentication handler
19
+ auth = APICAuth(
20
+ controller_url="https://apic.example.com",
21
+ username="admin",
22
+ password="password123"
23
+ )
24
+
25
+ # Use in a test class
26
+ class MyACITest(APICTestBase):
27
+ def setup(self):
28
+ self.auth = auth
29
+ super().setup()
30
+ ```
31
+
32
+ Note:
33
+ This module is specifically designed for testing ACI environments and requires
34
+ proper APIC controller access and credentials for full functionality.
35
+ """
36
+
37
+ from .auth import APICAuth
38
+ from .test_base import APICTestBase
39
+
40
+ __all__ = ["APICAuth", "APICTestBase"]
@@ -0,0 +1,157 @@
1
+ """APIC authentication module for Cisco ACI (Application Centric Infrastructure).
2
+
3
+ This module provides authentication functionality for Cisco APIC (Application Policy
4
+ Infrastructure Controller), which is the central management and policy enforcement
5
+ point for ACI fabric. The authentication mechanism uses REST API calls to obtain
6
+ session tokens that are valid for a limited time period.
7
+
8
+ The module implements a two-tier API design:
9
+ 1. authenticate() - Low-level method that performs direct APIC authentication
10
+ 2. get_token() - High-level method that leverages caching for efficient token reuse
11
+
12
+ This design ensures efficient token management by reusing valid tokens and only
13
+ re-authenticating when necessary, reducing unnecessary API calls to the APIC controller.
14
+ """
15
+
16
+ import httpx
17
+ from nac_test.pyats_core.common.auth_cache import AuthCache # type: ignore[import-untyped]
18
+
19
+ # Default token lifetime for APIC authentication tokens in seconds
20
+ # APIC tokens are typically valid for 10 minutes (600 seconds) by default
21
+ APIC_TOKEN_LIFETIME_SECONDS: int = 600
22
+
23
+
24
+ class APICAuth:
25
+ """APIC-specific authentication implementation with token caching.
26
+
27
+ This class provides a two-tier API for APIC authentication:
28
+
29
+ 1. Low-level authenticate() method: Directly authenticates with APIC and returns
30
+ a token along with its expiration time. This is typically used by the caching
31
+ layer and not called directly by consumers.
32
+
33
+ 2. High-level get_token() method: Provides cached token management, automatically
34
+ handling token renewal when expired. This is the primary method that consumers
35
+ should use for obtaining APIC tokens.
36
+
37
+ The two-tier design ensures efficient token reuse across multiple API calls while
38
+ maintaining clean separation between authentication logic and caching concerns.
39
+ """
40
+
41
+ @staticmethod
42
+ def authenticate(url: str, username: str, password: str) -> tuple[str, int]:
43
+ """Perform direct APIC authentication and obtain a session token.
44
+
45
+ This method performs a direct authentication request to the APIC controller
46
+ using the provided credentials. It returns both the token and its lifetime
47
+ for proper cache management.
48
+
49
+ Args:
50
+ url: Base URL of the APIC controller (e.g., "https://apic.example.com").
51
+ Should not include trailing slashes or API paths.
52
+ username: APIC username for authentication. This should be a valid user
53
+ configured in the APIC with appropriate permissions.
54
+ password: Password for the specified APIC user account.
55
+
56
+ Returns:
57
+ A tuple containing:
58
+ - token (str): The APIC session token that should be included in
59
+ subsequent API requests as a cookie (APIC-cookie).
60
+ - expires_in (int): Token lifetime in seconds (typically 600 seconds).
61
+
62
+ Raises:
63
+ httpx.HTTPStatusError: If the APIC returns a non-2xx status code,
64
+ typically indicating authentication failure (401) or server error.
65
+ httpx.RequestError: If the request fails due to network issues,
66
+ connection timeouts, or other transport-level problems.
67
+ KeyError: If the APIC response doesn't contain the expected JSON
68
+ structure with token information.
69
+ ValueError: If the APIC response contains malformed JSON that cannot
70
+ be parsed.
71
+ """
72
+ # NOTE: SSL verification is disabled (verify=False) to handle self-signed
73
+ # certificates commonly used in lab and development APIC deployments.
74
+ # In production environments, proper certificate validation should be enabled
75
+ # by either installing the APIC certificate in the trust store or providing
76
+ # a custom CA bundle via the verify parameter.
77
+ with httpx.Client(verify=False) as client:
78
+ response = client.post(
79
+ f"{url}/api/aaaLogin.json",
80
+ json={"aaaUser": {"attributes": {"name": username, "pwd": password}}},
81
+ )
82
+ response.raise_for_status()
83
+
84
+ # Parse the APIC response and extract the token
85
+ # Response structure: {"imdata": [{"aaaLogin": {"attributes": {"token": "..."}}}]}
86
+ try:
87
+ response_data = response.json()
88
+ token = response_data["imdata"][0]["aaaLogin"]["attributes"]["token"]
89
+ except (KeyError, IndexError) as e:
90
+ # Provide a more informative error message for malformed responses
91
+ raise ValueError(
92
+ f"APIC returned unexpected response structure. "
93
+ f"Expected JSON with 'imdata[0].aaaLogin.attributes.token' path. "
94
+ f"Actual response: {response.text[:500]}"
95
+ ) from e
96
+ except ValueError as e:
97
+ # Handle JSON parsing errors explicitly
98
+ raise ValueError(
99
+ f"APIC returned invalid JSON response: {response.text[:500]}"
100
+ ) from e
101
+
102
+ return token, APIC_TOKEN_LIFETIME_SECONDS
103
+
104
+ @classmethod
105
+ def get_token(cls, url: str, username: str, password: str) -> str:
106
+ """Get APIC token with automatic caching and renewal.
107
+
108
+ This is the primary method that consumers should use to obtain APIC tokens.
109
+ It leverages the AuthCache to efficiently manage token lifecycle, reusing
110
+ valid tokens and automatically renewing expired ones. This significantly
111
+ reduces the number of authentication requests to the APIC controller.
112
+
113
+ The method uses a cache key based on the controller type ("ACI"), URL,
114
+ and username to ensure proper token isolation between different APIC
115
+ instances and user accounts.
116
+
117
+ Args:
118
+ url: Base URL of the APIC controller (e.g., "https://apic.example.com").
119
+ Should not include trailing slashes or API paths.
120
+ username: APIC username for authentication. This should be a valid user
121
+ configured in the APIC with appropriate permissions.
122
+ password: Password for the specified APIC user account.
123
+
124
+ Returns:
125
+ A valid APIC session token that can be used in API requests.
126
+ The token should be included as a cookie (APIC-cookie) in subsequent
127
+ API calls to the APIC controller.
128
+
129
+ Raises:
130
+ httpx.HTTPStatusError: If the APIC returns a non-2xx status code during
131
+ authentication, typically indicating invalid credentials (401) or
132
+ server issues (5xx).
133
+ httpx.RequestError: If the request fails due to network issues,
134
+ connection timeouts, or other transport-level problems.
135
+ ValueError: If the APIC response contains malformed or unexpected JSON
136
+ structure that cannot be properly parsed.
137
+
138
+ Examples:
139
+ >>> # Get a token for APIC access
140
+ >>> token = APICAuth.get_token(
141
+ ... url="https://apic.example.com",
142
+ ... username="admin",
143
+ ... password="password123"
144
+ ... )
145
+ >>> # Use the token in subsequent API calls
146
+ >>> headers = {"Cookie": f"APIC-cookie={token}"}
147
+ """
148
+ # AuthCache.get_or_create_token returns str, but mypy can't verify this
149
+ # because nac_test lacks py.typed marker. The return type is guaranteed
150
+ # by AuthCache's implementation which uses extract_token=True mode.
151
+ return AuthCache.get_or_create_token( # type: ignore[no-any-return]
152
+ controller_type="ACI",
153
+ url=url,
154
+ username=username,
155
+ password=password,
156
+ auth_func=cls.authenticate,
157
+ )
@@ -0,0 +1,138 @@
1
+ """APIC-specific base test class for ACI API testing.
2
+
3
+ This module provides the APICTestBase class, which extends the generic NACTestBase
4
+ to add ACI-specific functionality for testing APIC controllers. It handles
5
+ authentication, client management, and provides a standardized interface for
6
+ running asynchronous verification tests against ACI fabrics.
7
+
8
+ The class integrates with PyATS/Genie test frameworks and provides automatic
9
+ API call tracking for enhanced HTML reporting.
10
+ """
11
+
12
+ import asyncio
13
+ from typing import Any
14
+
15
+ import httpx
16
+ from nac_test.pyats_core.common.base_test import NACTestBase # type: ignore[import-untyped]
17
+ from pyats import aetest # type: ignore[import-untyped]
18
+
19
+ from .auth import APICAuth
20
+
21
+
22
+ class APICTestBase(NACTestBase): # type: ignore[misc]
23
+ """Base class for APIC API tests with enhanced reporting.
24
+
25
+ This class extends the generic NACTestBase to provide APIC-specific
26
+ functionality including APIC authentication token management, API call
27
+ tracking for HTML reports, and wrapped HTTP client for automatic response
28
+ capture. It serves as the foundation for all ACI-specific test classes.
29
+
30
+ Attributes:
31
+ token (str): APIC authentication token obtained during setup.
32
+ client (httpx.AsyncClient): Wrapped async HTTP client configured for APIC.
33
+ controller_url (str): Base URL of the APIC controller (inherited).
34
+ username (str): APIC username for authentication (inherited).
35
+ password (str): APIC password for authentication (inherited).
36
+
37
+ Methods:
38
+ setup(): Initialize APIC authentication and client.
39
+ get_apic_client(): Create and configure an APIC-specific HTTP client.
40
+ run_async_verification_test(): Execute async verification tests with PyATS.
41
+
42
+ Example:
43
+ class MyAPICTest(APICTestBase):
44
+ async def get_items_to_verify(self):
45
+ return ['tenant1', 'tenant2']
46
+
47
+ async def verify_item(self, item):
48
+ # Custom verification logic here
49
+ pass
50
+
51
+ @aetest.test
52
+ def verify_tenants(self, steps):
53
+ self.run_async_verification_test(steps)
54
+ """
55
+
56
+ @aetest.setup # type: ignore[misc, untyped-decorator]
57
+ def setup(self) -> None:
58
+ """Setup method that extends the generic base class setup.
59
+
60
+ Initializes the APIC test environment by:
61
+ 1. Calling the parent class setup method
62
+ 2. Obtaining an APIC authentication token using file-based locking
63
+ 3. Creating and storing an APIC client for use in verification methods
64
+
65
+ The authentication token is obtained through the APICAuth utility which
66
+ manages token lifecycle and prevents duplicate authentication requests
67
+ across parallel test execution.
68
+ """
69
+ super().setup()
70
+
71
+ # Get shared APIC token using file-based locking
72
+ self.token = APICAuth.get_token(self.controller_url, self.username, self.password)
73
+
74
+ # Store the APIC client for use in verification methods
75
+ self.client = self.get_apic_client()
76
+
77
+ def get_apic_client(self) -> httpx.AsyncClient:
78
+ """Get an httpx async client configured for APIC with response tracking.
79
+
80
+ Creates an HTTP client specifically configured for APIC API communication
81
+ with authentication headers, base URL, and automatic response tracking
82
+ for HTML report generation. The client is wrapped to capture all API
83
+ interactions for detailed test reporting.
84
+
85
+ Returns:
86
+ httpx.AsyncClient: Configured client with APIC authentication, base URL,
87
+ and wrapped for automatic API call tracking. The client has SSL
88
+ verification disabled for lab environment compatibility.
89
+
90
+ Note:
91
+ SSL verification is disabled (verify=False) to support lab environments
92
+ with self-signed certificates. For production environments, consider
93
+ enabling SSL verification with proper certificate management.
94
+ """
95
+ headers = {"Cookie": f"APIC-cookie={self.token}"}
96
+ # SSL verification disabled for lab environment compatibility
97
+ client = self.pool.get_client(base_url=self.controller_url, headers=headers, verify=False)
98
+
99
+ # Use the generic tracking wrapper from base class
100
+ return self.wrap_client_for_tracking(client, device_name="APIC") # type: ignore[no-any-return]
101
+
102
+ def run_async_verification_test(self, steps: Any) -> None:
103
+ """Execute asynchronous verification tests with PyATS step tracking.
104
+
105
+ Simple entry point that uses base class orchestration to run async
106
+ verification tests. This thin wrapper:
107
+ 1. Creates and manages an event loop for async operations
108
+ 2. Calls NACTestBase.run_verification_async() to execute tests
109
+ 3. Passes results to NACTestBase.process_results_smart() for reporting
110
+ 4. Ensures proper cleanup of async resources
111
+
112
+ The actual verification logic is handled by:
113
+ - get_items_to_verify() - must be implemented by the test class
114
+ - verify_item() - must be implemented by the test class
115
+
116
+ Args:
117
+ steps: PyATS steps object for test reporting and step management.
118
+ Each verification item will be executed as a separate step
119
+ with automatic pass/fail tracking.
120
+
121
+ Note:
122
+ This method creates its own event loop to ensure compatibility
123
+ with PyATS synchronous test execution model. The loop and client
124
+ connections are properly closed after test completion.
125
+ """
126
+ loop = asyncio.new_event_loop()
127
+ asyncio.set_event_loop(loop)
128
+ try:
129
+ # Call the base class generic orchestration
130
+ results = loop.run_until_complete(self.run_verification_async())
131
+
132
+ # Process results using smart configuration-driven processing
133
+ self.process_results_smart(results, steps)
134
+ finally:
135
+ # Clean up the APIC client connection
136
+ if hasattr(self, "client"):
137
+ loop.run_until_complete(self.client.aclose())
138
+ loop.close()
@@ -0,0 +1,35 @@
1
+ """Catalyst Center adapter module for NAC PyATS testing.
2
+
3
+ This module provides Catalyst Center-specific authentication and test base class
4
+ implementations for use with the nac-test framework. Catalyst Center (formerly
5
+ DNA Center) is Cisco's enterprise network management platform.
6
+
7
+ Classes:
8
+ CatalystCenterAuth: Token-based authentication with automatic endpoint detection.
9
+ CatalystCenterTestBase: Base class for Catalyst Center API tests with tracking.
10
+
11
+ Example:
12
+ >>> from nac_test_pyats_common.catc import CatalystCenterTestBase
13
+ >>>
14
+ >>> class VerifyNetworkDevices(CatalystCenterTestBase):
15
+ ... async def get_items_to_verify(self):
16
+ ... return ['device-uuid-1', 'device-uuid-2']
17
+ ...
18
+ ... async def verify_item(self, item):
19
+ ... response = await self.client.get(
20
+ ... f"/dna/intent/api/v1/network-device/{item}"
21
+ ... )
22
+ ... return response.status_code == 200
23
+ ...
24
+ ... @aetest.test
25
+ ... def verify_devices(self, steps):
26
+ ... self.run_async_verification_test(steps)
27
+ """
28
+
29
+ from .auth import CatalystCenterAuth
30
+ from .test_base import CatalystCenterTestBase
31
+
32
+ __all__ = [
33
+ "CatalystCenterAuth",
34
+ "CatalystCenterTestBase",
35
+ ]
@@ -0,0 +1,205 @@
1
+ """Catalyst Center-specific authentication implementation.
2
+
3
+ This module provides authentication functionality for Cisco Catalyst Center
4
+ (formerly DNA Center), which is the central management platform for enterprise
5
+ networks. The authentication mechanism uses token-based login with Basic Auth.
6
+
7
+ The module implements a two-tier API design:
8
+ 1. _authenticate() - Low-level method that performs direct Catalyst Center authentication
9
+ 2. get_auth() - High-level method that leverages caching for efficient token reuse
10
+
11
+ This design ensures efficient token management by reusing valid tokens and only
12
+ re-authenticating when necessary, reducing unnecessary API calls to the controller.
13
+ """
14
+
15
+ import os
16
+ from typing import Any
17
+
18
+ import httpx
19
+ from nac_test.pyats_core.common.auth_cache import AuthCache # type: ignore[import-untyped]
20
+
21
+ # Default token lifetime for Catalyst Center authentication in seconds
22
+ # Catalyst Center tokens are typically valid for 1 hour (3600 seconds) by default
23
+ CATALYST_CENTER_TOKEN_LIFETIME_SECONDS: int = 3600
24
+
25
+ # HTTP timeout for authentication request
26
+ AUTH_REQUEST_TIMEOUT_SECONDS: float = 30.0
27
+
28
+ # Auth endpoints (try modern first, fallback to legacy)
29
+ AUTH_ENDPOINTS: list[str] = [
30
+ "/api/system/v1/auth/token", # Modern (Catalyst Center 2.x)
31
+ "/dna/system/api/v1/auth/token", # Legacy (DNA Center 1.x/2.x)
32
+ ]
33
+
34
+
35
+ class CatalystCenterAuth:
36
+ """Catalyst Center-specific authentication implementation with token caching.
37
+
38
+ This class provides a two-tier API for Catalyst Center authentication:
39
+
40
+ 1. Low-level _authenticate() method: Directly authenticates with Catalyst Center
41
+ using Basic Auth and returns token data along with expiration time. This is
42
+ typically used by the caching layer and not called directly by consumers.
43
+
44
+ 2. High-level get_auth() method: Provides cached token management, automatically
45
+ handling token renewal when expired. This is the primary method that consumers
46
+ should use for obtaining Catalyst Center tokens.
47
+
48
+ The authentication flow supports both:
49
+ - Modern Catalyst Center 2.x: /api/system/v1/auth/token endpoint
50
+ - Legacy DNA Center 1.x/2.x: /dna/system/api/v1/auth/token endpoint
51
+
52
+ The class mirrors VManageAuth pattern for consistency across NAC adapters.
53
+
54
+ Example:
55
+ >>> # Get authentication data for Catalyst Center API calls
56
+ >>> auth_data = CatalystCenterAuth.get_auth()
57
+ >>> # Use in requests
58
+ >>> headers = {"X-Auth-Token": auth_data["token"]}
59
+ """
60
+
61
+ @classmethod
62
+ def _authenticate(
63
+ cls, url: str, username: str, password: str, verify_ssl: bool
64
+ ) -> tuple[dict[str, Any], int]:
65
+ """Perform direct Catalyst Center authentication and obtain token.
66
+
67
+ This method performs a direct authentication request to the Catalyst Center
68
+ using Basic Auth. It tries the modern auth endpoint first, then falls back
69
+ to the legacy endpoint if needed for backward compatibility.
70
+
71
+ Args:
72
+ url: Base URL of the Catalyst Center (e.g., "https://catc.example.com").
73
+ Should not include trailing slashes or API paths.
74
+ username: Catalyst Center username for authentication. This should be
75
+ a valid user configured with appropriate permissions.
76
+ password: Password for the specified Catalyst Center user account.
77
+ verify_ssl: Whether to verify SSL certificates. Set to False for
78
+ lab environments with self-signed certificates.
79
+
80
+ Returns:
81
+ A tuple containing:
82
+ - auth_dict (dict): Dictionary with 'token' (str) containing the
83
+ authentication token for API requests.
84
+ - expires_in (int): Token lifetime in seconds (typically 3600).
85
+
86
+ Raises:
87
+ httpx.HTTPStatusError: If Catalyst Center returns a non-2xx status code
88
+ on all auth endpoints, typically indicating authentication failure.
89
+ RuntimeError: If authentication fails on all available endpoints.
90
+ ValueError: If the token is not received in the response from any endpoint.
91
+
92
+ Note:
93
+ SSL verification can be disabled via the verify_ssl parameter to handle
94
+ self-signed certificates commonly used in lab deployments. In production
95
+ environments, proper certificate validation should be enabled.
96
+ """
97
+ last_error: Exception | None = None
98
+
99
+ with httpx.Client(verify=verify_ssl, timeout=AUTH_REQUEST_TIMEOUT_SECONDS) as client:
100
+ for endpoint in AUTH_ENDPOINTS:
101
+ try:
102
+ auth_response = client.post(
103
+ f"{url}{endpoint}",
104
+ auth=(username, password),
105
+ headers={
106
+ "Content-Type": "application/json",
107
+ "Accept": "application/json",
108
+ },
109
+ )
110
+ auth_response.raise_for_status()
111
+
112
+ # Extract token from response
113
+ response_data = auth_response.json()
114
+ token = response_data.get("Token")
115
+
116
+ if not token:
117
+ raise ValueError(
118
+ f"No 'Token' field in auth response from {endpoint}. "
119
+ f"Response keys: {list(response_data.keys())}"
120
+ )
121
+
122
+ # Return auth data with token lifetime
123
+ return {"token": str(token)}, CATALYST_CENTER_TOKEN_LIFETIME_SECONDS
124
+
125
+ except (httpx.HTTPError, ValueError) as e:
126
+ last_error = e
127
+ # Try next endpoint
128
+ continue
129
+
130
+ # All endpoints failed
131
+ raise RuntimeError(
132
+ f"Catalyst Center authentication failed on all endpoints. Last error: {last_error}"
133
+ ) from last_error
134
+
135
+ @classmethod
136
+ def get_auth(cls) -> dict[str, Any]:
137
+ """Get Catalyst Center authentication data with automatic caching and renewal.
138
+
139
+ This is the primary method that consumers should use to obtain Catalyst Center
140
+ tokens. It leverages the AuthCache to efficiently manage token lifecycle,
141
+ reusing valid tokens and automatically renewing expired ones. This significantly
142
+ reduces the number of authentication requests to the Catalyst Center.
143
+
144
+ The method uses a cache key based on the controller type ("CC") and URL
145
+ to ensure proper token isolation between different Catalyst Center instances.
146
+
147
+ Environment Variables Required:
148
+ CC_URL: Base URL of the Catalyst Center
149
+ CC_USERNAME: Catalyst Center username for authentication
150
+ CC_PASSWORD: Catalyst Center password for authentication
151
+ CC_INSECURE: Optional. Set to "True" to disable SSL verification (default: True)
152
+
153
+ Returns:
154
+ A dictionary containing:
155
+ - token (str): The authentication token for API requests.
156
+ Should be included as X-Auth-Token header in subsequent calls.
157
+
158
+ Raises:
159
+ ValueError: If any required environment variables (CC_URL, CC_USERNAME,
160
+ CC_PASSWORD) are not set.
161
+ RuntimeError: If authentication fails on all available endpoints.
162
+ httpx.HTTPStatusError: If Catalyst Center returns authentication errors.
163
+
164
+ Example:
165
+ >>> # Set environment variables first
166
+ >>> import os
167
+ >>> os.environ["CC_URL"] = "https://catalyst.example.com"
168
+ >>> os.environ["CC_USERNAME"] = "admin"
169
+ >>> os.environ["CC_PASSWORD"] = "password123"
170
+ >>> os.environ["CC_INSECURE"] = "True" # For lab environments
171
+ >>> # Get authentication data
172
+ >>> auth_data = CatalystCenterAuth.get_auth()
173
+ >>> # Use in API requests
174
+ >>> headers = {"X-Auth-Token": auth_data["token"]}
175
+ """
176
+ url = os.environ.get("CC_URL")
177
+ username = os.environ.get("CC_USERNAME")
178
+ password = os.environ.get("CC_PASSWORD")
179
+ insecure = os.environ.get("CC_INSECURE", "True").lower() in ("true", "1", "yes")
180
+
181
+ if not all([url, username, password]):
182
+ missing_vars: list[str] = []
183
+ if not url:
184
+ missing_vars.append("CC_URL")
185
+ if not username:
186
+ missing_vars.append("CC_USERNAME")
187
+ if not password:
188
+ missing_vars.append("CC_PASSWORD")
189
+ raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
190
+
191
+ # Normalize URL by removing trailing slash
192
+ url = url.rstrip("/") # type: ignore[union-attr]
193
+ verify_ssl = not insecure # CC_INSECURE=True means verify=False
194
+
195
+ def auth_wrapper() -> tuple[dict[str, Any], int]:
196
+ """Wrapper for authentication that captures closure variables."""
197
+ return cls._authenticate(url, username, password, verify_ssl) # type: ignore[arg-type]
198
+
199
+ # AuthCache.get_or_create returns dict[str, Any], but mypy can't verify this
200
+ # because nac_test lacks py.typed marker.
201
+ return AuthCache.get_or_create( # type: ignore[no-any-return]
202
+ controller_type="CC",
203
+ url=url,
204
+ auth_func=auth_wrapper,
205
+ )