-
Notifications
You must be signed in to change notification settings - Fork 2
0.61.4 #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
0.61.4 #22
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| [submodule "mist_openapi"] | ||
| path = mist_openapi | ||
| url = https://github.com/mistsys/mist_openapi.git | ||
| branch = 2602.1.8 | ||
| branch = master | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -157,12 +157,28 @@ def __init__( | |||||||||||
| self._session.proxies.update(filtered_proxies) | ||||||||||||
|
|
||||||||||||
| if host: | ||||||||||||
| if self._cloud_uri: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:__init__: overriding previously loaded MIST_HOST with constructor parameter" | ||||||||||||
| ) | ||||||||||||
| self.set_cloud(host) | ||||||||||||
| if email: | ||||||||||||
| if self.email: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:__init__: overriding previously loaded MIST_USER with constructor parameter" | ||||||||||||
| ) | ||||||||||||
| self.set_email(email) | ||||||||||||
| if password: | ||||||||||||
| if self._password: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:__init__: overriding previously loaded MIST_PASSWORD with constructor parameter" | ||||||||||||
| ) | ||||||||||||
| self.set_password(password) | ||||||||||||
| if apitoken: | ||||||||||||
| if self._apitoken: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:__init__: overriding previously loaded MIST_APITOKEN with constructor parameter" | ||||||||||||
| ) | ||||||||||||
| self.set_api_token(apitoken) | ||||||||||||
| self.first_name: str = "" | ||||||||||||
| self.last_name: str = "" | ||||||||||||
|
|
@@ -244,10 +260,18 @@ def _load_vault( | |||||||||||
| mist_host = read_response["data"]["data"].get("MIST_HOST", None) | ||||||||||||
| LOGGER.info("apisession:_load_vault: MIST_HOST=%s", mist_host) | ||||||||||||
| if mist_host: | ||||||||||||
| if self._cloud_uri: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_vault: overriding previously loaded MIST_HOST" | ||||||||||||
| ) | ||||||||||||
| self.set_cloud(mist_host) | ||||||||||||
|
|
||||||||||||
| mist_apitoken = read_response["data"]["data"].get("MIST_APITOKEN", None) | ||||||||||||
| if mist_apitoken: | ||||||||||||
| if self._apitoken: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_vault: overriding previously loaded MIST_APITOKEN" | ||||||||||||
| ) | ||||||||||||
| self.set_api_token(mist_apitoken) | ||||||||||||
| except (KeyError, TypeError, AttributeError): | ||||||||||||
| LOGGER.error("apisession:_load_vault: Failed to retrieve secret") | ||||||||||||
|
|
@@ -270,10 +294,18 @@ def _load_keyring(self, keyring_service) -> None: | |||||||||||
| try: | ||||||||||||
| mist_host = keyring.get_password(keyring_service, "MIST_HOST") | ||||||||||||
| if mist_host: | ||||||||||||
| if self._cloud_uri: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_keyring: overriding previously loaded MIST_HOST" | ||||||||||||
| ) | ||||||||||||
| LOGGER.info("apisession:_load_keyring: MIST_HOST=%s", mist_host) | ||||||||||||
| self.set_cloud(mist_host) | ||||||||||||
| mist_apitoken = keyring.get_password(keyring_service, "MIST_APITOKEN") | ||||||||||||
| if mist_apitoken: | ||||||||||||
| if self._apitoken: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_keyring: overriding previously loaded MIST_APITOKEN" | ||||||||||||
| ) | ||||||||||||
| if isinstance(mist_apitoken, str): | ||||||||||||
| for token in mist_apitoken.split(","): | ||||||||||||
| token = token.strip() | ||||||||||||
|
|
@@ -285,10 +317,18 @@ def _load_keyring(self, keyring_service) -> None: | |||||||||||
| self.set_api_token(mist_apitoken) | ||||||||||||
| mist_user = keyring.get_password(keyring_service, "MIST_USER") | ||||||||||||
| if mist_user: | ||||||||||||
| if self.email: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_keyring: overriding previously loaded MIST_USER" | ||||||||||||
| ) | ||||||||||||
| LOGGER.info("apisession:_load_keyring: MIST_USER retrieved") | ||||||||||||
| self.set_email(mist_user) | ||||||||||||
| mist_password = keyring.get_password(keyring_service, "MIST_PASSWORD") | ||||||||||||
| if mist_password: | ||||||||||||
| if self._password: | ||||||||||||
| LOGGER.info( | ||||||||||||
| "apisession:_load_keyring: overriding previously loaded MIST_PASSWORD" | ||||||||||||
| ) | ||||||||||||
| LOGGER.info("apisession:_load_keyring: MIST_PASSWORD retrieved") | ||||||||||||
| self.set_password(mist_password) | ||||||||||||
| except Exception as e: | ||||||||||||
|
|
@@ -701,6 +741,7 @@ def _process_login(self, retry: bool = True) -> str | None: | |||||||||||
| if resp.status_code == 200: | ||||||||||||
| LOGGER.info("apisession:_process_login:authentication successful!") | ||||||||||||
| CONSOLE.info("Authentication successful!") | ||||||||||||
| self._password = None | ||||||||||||
| self._set_authenticated(True) | ||||||||||||
| else: | ||||||||||||
| error = resp.json().get("detail") | ||||||||||||
|
|
@@ -818,14 +859,22 @@ def login_with_return( | |||||||||||
| elif self.email and self._password: | ||||||||||||
| if two_factor: | ||||||||||||
| LOGGER.debug("apisession:login_with_return:login/pwd provided with 2FA") | ||||||||||||
| if self._two_factor_authentication(two_factor): | ||||||||||||
| error_login = self._process_login(retry=False) | ||||||||||||
| if error_login: | ||||||||||||
| LOGGER.error( | ||||||||||||
| "apisession:login_with_return:login/pwd auth failed: %s", | ||||||||||||
| error_login, | ||||||||||||
| ) | ||||||||||||
| return {"authenticated": False, "error": error_login} | ||||||||||||
| if not self._two_factor_authentication(two_factor): | ||||||||||||
| LOGGER.error( | ||||||||||||
| "apisession:login_with_return:login/pwd auth failed: 2FA authentication failed" | ||||||||||||
| "apisession:login_with_return:2FA authentication failed" | ||||||||||||
| ) | ||||||||||||
tmunzer-AIDE marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
| return { | ||||||||||||
| "authenticated": False, | ||||||||||||
| "error": "2FA authentication failed", | ||||||||||||
| } | ||||||||||||
| LOGGER.info("apisession:login_with_return:authenticated with 2FA") | ||||||||||||
| else: | ||||||||||||
| LOGGER.debug("apisession:login_with_return:login/pwd provided w/o 2FA") | ||||||||||||
| error_login = self._process_login(retry=False) | ||||||||||||
|
|
@@ -1044,10 +1093,8 @@ def _two_factor_authentication(self, two_factor: str) -> bool: | |||||||||||
| True if authentication succeed, False otherwise | ||||||||||||
| """ | ||||||||||||
| LOGGER.debug("apisession:_two_factor_authentication") | ||||||||||||
| uri = "/api/v1/login" | ||||||||||||
| uri = "/api/v1/login/two_factor" | ||||||||||||
| body = { | ||||||||||||
| "email": self.email, | ||||||||||||
| "password": self._password, | ||||||||||||
| "two_factor": two_factor, | ||||||||||||
| } | ||||||||||||
| resp = self._session.post(self._url(uri), json=body) | ||||||||||||
|
|
@@ -1102,7 +1149,19 @@ def _getself(self) -> bool: | |||||||||||
| elif key == "tags": | ||||||||||||
| for tag in resp.data["tags"]: | ||||||||||||
| self.tags.append(tag) | ||||||||||||
| else: | ||||||||||||
| elif key in [ | ||||||||||||
| "first_name", | ||||||||||||
| "last_name", | ||||||||||||
| "email", | ||||||||||||
tmunzer-AIDE marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
| "phone", | ||||||||||||
| "enable_two_factor", | ||||||||||||
| "two_factor_verified", | ||||||||||||
| "no_tracking", | ||||||||||||
| "oauth_google", | ||||||||||||
| "password_expiry", | ||||||||||||
| "password_modified_time", | ||||||||||||
|
||||||||||||
| "password_modified_time", | |
| "password_modified_time", | |
| "via_sso", | |
| "phone", | |
| "session_expiry", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,2 @@ | ||
| __version__ = "0.61.3" | ||
| __version__ = "0.61.4" | ||
| __author__ = "Thomas Munzer <tmunzer@juniper.net>" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,12 +69,15 @@ def __init__( | |
| auto_reconnect: bool = False, | ||
| max_reconnect_attempts: int = 5, | ||
| reconnect_backoff: float = 2.0, | ||
| max_reconnect_backoff: float | None = None, | ||
| queue_maxsize: int = 0, | ||
| ) -> None: | ||
| if max_reconnect_attempts < 0: | ||
| raise ValueError("max_reconnect_attempts must be >= 0") | ||
| raise ValueError("max_reconnect_attempts must be >= 0 (0 = unlimited)") | ||
| if reconnect_backoff <= 0: | ||
| raise ValueError("reconnect_backoff must be > 0") | ||
| if max_reconnect_backoff is not None and max_reconnect_backoff <= 0: | ||
| raise ValueError("max_reconnect_backoff must be > 0") | ||
|
Comment on lines
69
to
+80
|
||
| if queue_maxsize < 0: | ||
| raise ValueError("queue_maxsize must be >= 0") | ||
|
|
||
|
|
@@ -85,6 +88,7 @@ def __init__( | |
| self._auto_reconnect = auto_reconnect | ||
| self._max_reconnect_attempts = max_reconnect_attempts | ||
| self._reconnect_backoff = reconnect_backoff | ||
| self._max_reconnect_backoff = max_reconnect_backoff | ||
| self._lock = threading.Lock() | ||
| self._ws: websocket.WebSocketApp | None = None | ||
| self._thread: threading.Thread | None = None | ||
|
|
@@ -305,22 +309,32 @@ def _run_forever_safe(self) -> None: | |
| break | ||
|
|
||
| self._reconnect_attempts += 1 | ||
| if self._reconnect_attempts > self._max_reconnect_attempts: | ||
| if ( | ||
| self._max_reconnect_attempts > 0 | ||
| and self._reconnect_attempts > self._max_reconnect_attempts | ||
| ): | ||
| logger.warning( | ||
| "Max reconnect attempts (%d) reached, giving up", | ||
| self._max_reconnect_attempts, | ||
| ) | ||
| break | ||
|
|
||
| delay = self._reconnect_backoff * ( | ||
| 2 ** (self._reconnect_attempts - 1) | ||
| ) | ||
| logger.info( | ||
| "Reconnecting in %.1fs (attempt %d/%d)", | ||
| delay, | ||
| self._reconnect_attempts, | ||
| self._max_reconnect_attempts, | ||
| ) | ||
| delay = self._reconnect_backoff * (2 ** (self._reconnect_attempts - 1)) | ||
| if self._max_reconnect_backoff is not None: | ||
| delay = min(delay, self._max_reconnect_backoff) | ||
| if self._max_reconnect_attempts > 0: | ||
| logger.info( | ||
| "Reconnecting in %.1fs (attempt %d/%d)", | ||
| delay, | ||
| self._reconnect_attempts, | ||
| self._max_reconnect_attempts, | ||
| ) | ||
| else: | ||
| logger.info( | ||
| "Reconnecting in %.1fs (attempt %d, unlimited)", | ||
| delay, | ||
| self._reconnect_attempts, | ||
| ) | ||
| if self._user_disconnect.wait(timeout=delay): | ||
| break # disconnect() called during backoff | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switching the mist_openapi submodule tracking from a versioned branch to master makes builds non-reproducible and can unexpectedly pull breaking API spec changes. Consider keeping this pinned to a specific release branch/tag/commit (or document why master is required) and reflect the spec version change in the changelog/release notes if it’s intentional.