From 76d4fac940365e03eae1aba357d748e48e8016eb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 8 Oct 2019 13:35:26 +0200 Subject: [PATCH] added support for muster login window, validator for muster connection, tweaks in muster dialog --- pype/muster/muster.py | 8 +- pype/muster/widget_login.py | 29 +++++- .../maya/create/create_renderglobals.py | 38 +++++--- .../publish/validate_muster_connection.py | 93 +++++++++++++++++++ 4 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 pype/plugins/maya/publish/validate_muster_connection.py diff --git a/pype/muster/muster.py b/pype/muster/muster.py index 2041ae5512..27cd6f73ec 100644 --- a/pype/muster/muster.py +++ b/pype/muster/muster.py @@ -38,9 +38,13 @@ class MusterModule: pass def process_modules(self, modules): + + def api_callback(): + self.aShowLogin.trigger() + if "RestApiServer" in modules: modules["RestApiServer"].register_callback( - "muster/show_login", self.show_login, "post" + "muster/show_login", api_callback, "post" ) # Definition of Tray menu @@ -61,7 +65,7 @@ class MusterModule: self.menu.addAction(self.aShowLogin) self.aShowLogin.triggered.connect(self.show_login) - return self.menu + parent.addMenu(self.menu) def load_credentials(self): """ diff --git a/pype/muster/widget_login.py b/pype/muster/widget_login.py index add4437026..1d0dd29d59 100644 --- a/pype/muster/widget_login.py +++ b/pype/muster/widget_login.py @@ -88,8 +88,7 @@ class MusterLogin(QtWidgets.QWidget): self.error_label = QtWidgets.QLabel("") self.error_label.setFont(self.font) - self.error_label.setTextFormat(QtCore.Qt.RichText) - self.error_label.setObjectName("error_label") + self.error_label.setStyleSheet('color: #FC6000') self.error_label.setWordWrap(True) self.error_label.hide() @@ -105,6 +104,9 @@ class MusterLogin(QtWidgets.QWidget): self.btn_ok.clicked.connect(self.click_ok) self.btn_cancel = QtWidgets.QPushButton("Cancel") + QtWidgets.QShortcut( + QtGui.QKeySequence( + QtCore.Qt.Key_Escape), self).activated.connect(self.close) self.btn_cancel.clicked.connect(self.close) self.btn_group.addWidget(self.btn_ok) @@ -115,7 +117,21 @@ class MusterLogin(QtWidgets.QWidget): return self.main + def keyPressEvent(self, key_event): + if key_event.key() == QtCore.Qt.Key_Return: + if self.input_username.hasFocus(): + self.input_password.setFocus() + + elif self.input_password.hasFocus() or self.btn_ok.hasFocus(): + self.click_ok() + + elif self.btn_cancel.hasFocus(): + self.close() + else: + super().keyPressEvent(key_event) + def setError(self, msg): + self.error_label.setText(msg) self.error_label.show() @@ -130,8 +146,13 @@ class MusterLogin(QtWidgets.QWidget): if not username: self.setError("Username cannot be empty") self.invalid_input(self.input_username) - self.save_credentials(username, password) - self._close_widget() + try: + self.save_credentials(username, password) + except Exception as e: + self.setError( + "Cannot get auth token:\n{}".format(e)) + else: + self._close_widget() def save_credentials(self, username, password): self.parent_widget.get_auth_token(username, password) diff --git a/pype/plugins/maya/create/create_renderglobals.py b/pype/plugins/maya/create/create_renderglobals.py index fb897a3f4f..368a8b181a 100644 --- a/pype/plugins/maya/create/create_renderglobals.py +++ b/pype/plugins/maya/create/create_renderglobals.py @@ -1,12 +1,10 @@ -from maya import cmds - -import pype.maya.lib as lib - -from avalon.vendor import requests -import avalon.maya import os import json import appdirs +import requests +from maya import cmds +import pype.maya.lib as lib +import avalon.maya class CreateRenderGlobals(avalon.maya.Creator): @@ -51,13 +49,17 @@ class CreateRenderGlobals(avalon.maya.Creator): self.data["secondaryPool"] = ["-"] + pools if muster_url is None: - self.log.warning("Muster REST API url not found.") + self.log.warning("Muster REST API URL not found.") else: self.log.info(">>> Loading Muster credentials ...") self._load_credentials() self.log.info(">>> Getting pools ...") try: pools = self._get_muster_pools() + except requests.exceptions.HTTPError as e: + print(e) + if e.startswith('401'): + self.log.warning('access token expired') except requests.exceptions.ConnectionError: self.log.error("Cannot connect to Muster API endpoint.") raise RuntimeError("Cannot connect to {}".format(muster_url)) @@ -131,13 +133,25 @@ class CreateRenderGlobals(avalon.maya.Creator): 'authToken': self._token } api_entry = '/api/pools/list' - response = requests.post( + response = requests.get( self.MUSTER_REST_URL + api_entry, params=params) if response.status_code != 200: - self.log.error( - 'Cannot get pools from Muster: {}'.format( - response.status_code)) - raise Exception('Cannot get pools from Muster') + if response.status_code == 401: + self.log.warning('Authentication token expired.') + # authentication token expired so we need to login to Muster + # again to get it. We use Pype API call to show login window. + api_url = "{}/muster/show_login".format( + os.environ["PYPE_REST_API_URL"]) + self.log.debug(api_url) + login_response = requests.post(api_url, timeout=1) + if login_response.status_code != 200: + self.log.error('Cannot show login form to Muster') + raise Exception('Cannot show login form to Muster') + else: + self.log.error( + 'Cannot get pools from Muster: {}'.format( + response.status_code)) + raise Exception('Cannot get pools from Muster') try: pools = response.json()['ResponseData']['pools'] except ValueError as e: diff --git a/pype/plugins/maya/publish/validate_muster_connection.py b/pype/plugins/maya/publish/validate_muster_connection.py new file mode 100644 index 0000000000..08bc83e0a0 --- /dev/null +++ b/pype/plugins/maya/publish/validate_muster_connection.py @@ -0,0 +1,93 @@ +import os +import json +import appdirs + +import pyblish.api +from avalon.vendor import requests +from pype.plugin import contextplugin_should_run +import pype.maya.action + + +class ValidateMusterConnection(pyblish.api.ContextPlugin): + """ + Validate Muster REST API Service is running and we have valid auth token + """ + + label = "Validate Muster REST API Service" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["renderlayer"] + token = None + if not os.environ.get("MUSTER_REST_URL"): + active = False + actions = [pype.api.RepairAction] + + def process(self, context): + + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + + # test if we have environment set (redundant as this plugin shouldn' + # be active otherwise). + try: + MUSTER_REST_URL = os.environ["MUSTER_REST_URL"] + except KeyError: + self.log.error("Muster REST API url not found.") + raise ValueError("Muster REST API url not found.") + + # Load credentials + try: + self._load_credentials() + except RuntimeError: + self.log.error("invalid or missing access token") + + assert self._token is not None, "Invalid or missing token" + + # We have token, lets do trivial query to web api to see if we can + # connect and access token is valid. + params = { + 'authToken': self._token + } + api_entry = '/api/pools/list' + response = requests.get( + MUSTER_REST_URL + api_entry, params=params) + assert response.status_code == 200, "invalid response from server" + assert response.json()['ResponseData'], "invalid data in response" + + def _load_credentials(self): + """ + Load Muster credentials from file and set `MUSTER_USER`, + `MUSTER_PASSWORD`, `MUSTER_REST_URL` is loaded from presets. + + .. todo:: + + Show login dialog if access token is invalid or missing. + """ + app_dir = os.path.normpath( + appdirs.user_data_dir('pype-app', 'pype') + ) + file_name = 'muster_cred.json' + fpath = os.path.join(app_dir, file_name) + file = open(fpath, 'r') + muster_json = json.load(file) + self._token = muster_json.get('token', None) + if not self._token: + raise RuntimeError("Invalid access token for Muster") + file.close() + self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") + if not self.MUSTER_REST_URL: + raise AttributeError("Muster REST API url not set") + + @classmethod + def repair(cls, instance): + """ + Renew authentication token by logging into Muster + """ + api_url = "{}/muster/show_login".format( + os.environ["PYPE_REST_API_URL"]) + cls.log.debug(api_url) + response = requests.post(api_url, timeout=1) + if response.status_code != 200: + cls.log.error('Cannot show login form to Muster') + raise Exception('Cannot show login form to Muster')