|
1 | 1 | # dapi/systems.py |
2 | 2 | from tapipy.tapis import Tapis |
3 | | -from tapipy.errors import BaseTapyException |
| 3 | +from tapipy.errors import BaseTapyException, UnauthorizedError, NotFoundError |
4 | 4 | from typing import List, Any, Optional |
5 | | -from .exceptions import SystemInfoError |
| 5 | +from .exceptions import SystemInfoError, CredentialError |
6 | 6 |
|
7 | 7 |
|
8 | 8 | def list_system_queues(t: Tapis, system_id: str, verbose: bool = True) -> List[Any]: |
@@ -95,3 +95,200 @@ def list_system_queues(t: Tapis, system_id: str, verbose: bool = True) -> List[A |
95 | 95 | raise SystemInfoError( |
96 | 96 | f"An unexpected error occurred while fetching queues for system '{system_id}': {e}" |
97 | 97 | ) from e |
| 98 | + |
| 99 | + |
| 100 | +def _resolve_username(t: Tapis, username: Optional[str] = None) -> str: |
| 101 | + """Resolve the effective username from an explicit parameter or the Tapis client. |
| 102 | +
|
| 103 | + Args: |
| 104 | + t: Authenticated Tapis client instance. |
| 105 | + username: Explicit username. If None, falls back to t.username. |
| 106 | +
|
| 107 | + Returns: |
| 108 | + The resolved username string. |
| 109 | +
|
| 110 | + Raises: |
| 111 | + ValueError: If username cannot be determined from either source. |
| 112 | + """ |
| 113 | + effective = username or getattr(t, "username", None) |
| 114 | + if not effective: |
| 115 | + raise ValueError( |
| 116 | + "Username must be provided or available on the Tapis client (t.username)." |
| 117 | + ) |
| 118 | + return effective |
| 119 | + |
| 120 | + |
| 121 | +def check_credentials( |
| 122 | + t: Tapis, system_id: str, username: Optional[str] = None |
| 123 | +) -> bool: |
| 124 | + """Check whether TMS credentials exist for a user on a Tapis system. |
| 125 | +
|
| 126 | + Args: |
| 127 | + t: Authenticated Tapis client instance. |
| 128 | + system_id: The ID of the Tapis system (e.g., 'frontera', 'stampede3'). |
| 129 | + username: The username to check. If None, auto-detected from t.username. |
| 130 | +
|
| 131 | + Returns: |
| 132 | + True if credentials exist, False if they do not. |
| 133 | +
|
| 134 | + Raises: |
| 135 | + ValueError: If system_id is empty or username cannot be determined. |
| 136 | + CredentialError: If an unexpected API error occurs during the check. |
| 137 | + """ |
| 138 | + if not system_id: |
| 139 | + raise ValueError("system_id cannot be empty.") |
| 140 | + |
| 141 | + effective_username = _resolve_username(t, username) |
| 142 | + |
| 143 | + try: |
| 144 | + t.systems.checkUserCredential( |
| 145 | + systemId=system_id, userName=effective_username |
| 146 | + ) |
| 147 | + return True |
| 148 | + except (UnauthorizedError, NotFoundError): |
| 149 | + return False |
| 150 | + except BaseTapyException as e: |
| 151 | + raise CredentialError( |
| 152 | + f"Failed to check credentials for user '{effective_username}' " |
| 153 | + f"on system '{system_id}': {e}" |
| 154 | + ) from e |
| 155 | + except Exception as e: |
| 156 | + raise CredentialError( |
| 157 | + f"Unexpected error checking credentials for user '{effective_username}' " |
| 158 | + f"on system '{system_id}': {e}" |
| 159 | + ) from e |
| 160 | + |
| 161 | + |
| 162 | +def establish_credentials( |
| 163 | + t: Tapis, |
| 164 | + system_id: str, |
| 165 | + username: Optional[str] = None, |
| 166 | + force: bool = False, |
| 167 | + verbose: bool = True, |
| 168 | +) -> None: |
| 169 | + """Establish TMS credentials for a user on a Tapis system. |
| 170 | +
|
| 171 | + Idempotent: if credentials already exist and force is False, no action is taken. |
| 172 | + Only systems with defaultAuthnMethod 'TMS_KEYS' are supported. |
| 173 | +
|
| 174 | + Args: |
| 175 | + t: Authenticated Tapis client instance. |
| 176 | + system_id: The ID of the Tapis system (e.g., 'frontera', 'stampede3'). |
| 177 | + username: The username. If None, auto-detected from t.username. |
| 178 | + force: If True, create credentials even if they already exist. |
| 179 | + verbose: If True, prints status messages. |
| 180 | +
|
| 181 | + Raises: |
| 182 | + ValueError: If system_id is empty or username cannot be determined. |
| 183 | + CredentialError: If the system does not use TMS_KEYS, if the system is |
| 184 | + not found, or if credential creation fails. |
| 185 | + """ |
| 186 | + if not system_id: |
| 187 | + raise ValueError("system_id cannot be empty.") |
| 188 | + |
| 189 | + effective_username = _resolve_username(t, username) |
| 190 | + |
| 191 | + # Verify system exists and uses TMS_KEYS authentication |
| 192 | + try: |
| 193 | + system_details = t.systems.getSystem(systemId=system_id) |
| 194 | + authn_method = getattr(system_details, "defaultAuthnMethod", None) |
| 195 | + except BaseTapyException as e: |
| 196 | + if hasattr(e, "response") and e.response and e.response.status_code == 404: |
| 197 | + raise CredentialError( |
| 198 | + f"System '{system_id}' not found." |
| 199 | + ) from e |
| 200 | + raise CredentialError( |
| 201 | + f"Failed to retrieve system '{system_id}': {e}" |
| 202 | + ) from e |
| 203 | + |
| 204 | + if authn_method != "TMS_KEYS": |
| 205 | + raise CredentialError( |
| 206 | + f"System '{system_id}' uses authentication method '{authn_method}', " |
| 207 | + f"not 'TMS_KEYS'. TMS credential management is only supported " |
| 208 | + f"for TMS_KEYS systems." |
| 209 | + ) |
| 210 | + |
| 211 | + # Check existing credentials unless force is True |
| 212 | + if not force: |
| 213 | + if check_credentials(t, system_id, effective_username): |
| 214 | + if verbose: |
| 215 | + print( |
| 216 | + f"Credentials already exist for user '{effective_username}' " |
| 217 | + f"on system '{system_id}'. No action taken." |
| 218 | + ) |
| 219 | + return |
| 220 | + |
| 221 | + # Create credentials |
| 222 | + try: |
| 223 | + t.systems.createUserCredential( |
| 224 | + systemId=system_id, |
| 225 | + userName=effective_username, |
| 226 | + createTmsKeys=True, |
| 227 | + ) |
| 228 | + if verbose: |
| 229 | + print( |
| 230 | + f"TMS credentials established for user '{effective_username}' " |
| 231 | + f"on system '{system_id}'." |
| 232 | + ) |
| 233 | + except BaseTapyException as e: |
| 234 | + raise CredentialError( |
| 235 | + f"Failed to create credentials for user '{effective_username}' " |
| 236 | + f"on system '{system_id}': {e}" |
| 237 | + ) from e |
| 238 | + except Exception as e: |
| 239 | + raise CredentialError( |
| 240 | + f"Unexpected error creating credentials for user '{effective_username}' " |
| 241 | + f"on system '{system_id}': {e}" |
| 242 | + ) from e |
| 243 | + |
| 244 | + |
| 245 | +def revoke_credentials( |
| 246 | + t: Tapis, |
| 247 | + system_id: str, |
| 248 | + username: Optional[str] = None, |
| 249 | + verbose: bool = True, |
| 250 | +) -> None: |
| 251 | + """Remove TMS credentials for a user on a Tapis system. |
| 252 | +
|
| 253 | + Idempotent: if credentials do not exist, no error is raised. |
| 254 | +
|
| 255 | + Args: |
| 256 | + t: Authenticated Tapis client instance. |
| 257 | + system_id: The ID of the Tapis system (e.g., 'frontera', 'stampede3'). |
| 258 | + username: The username. If None, auto-detected from t.username. |
| 259 | + verbose: If True, prints status messages. |
| 260 | +
|
| 261 | + Raises: |
| 262 | + ValueError: If system_id is empty or username cannot be determined. |
| 263 | + CredentialError: If credential removal fails unexpectedly. |
| 264 | + """ |
| 265 | + if not system_id: |
| 266 | + raise ValueError("system_id cannot be empty.") |
| 267 | + |
| 268 | + effective_username = _resolve_username(t, username) |
| 269 | + |
| 270 | + try: |
| 271 | + t.systems.removeUserCredential( |
| 272 | + systemId=system_id, userName=effective_username |
| 273 | + ) |
| 274 | + if verbose: |
| 275 | + print( |
| 276 | + f"Credentials revoked for user '{effective_username}' " |
| 277 | + f"on system '{system_id}'." |
| 278 | + ) |
| 279 | + except (UnauthorizedError, NotFoundError): |
| 280 | + if verbose: |
| 281 | + print( |
| 282 | + f"No credentials found for user '{effective_username}' " |
| 283 | + f"on system '{system_id}'. No action taken." |
| 284 | + ) |
| 285 | + except BaseTapyException as e: |
| 286 | + raise CredentialError( |
| 287 | + f"Failed to revoke credentials for user '{effective_username}' " |
| 288 | + f"on system '{system_id}': {e}" |
| 289 | + ) from e |
| 290 | + except Exception as e: |
| 291 | + raise CredentialError( |
| 292 | + f"Unexpected error revoking credentials for user '{effective_username}' " |
| 293 | + f"on system '{system_id}': {e}" |
| 294 | + ) from e |
0 commit comments