def cloud_set_robot_contents(self, directory: str, workspace_id: str, robot_id: str) -> ActionResult: if not os.path.exists(directory): return ActionResult( False, f"Expected: {directory} to exist to upload to the cloud.") if not os.path.isdir(directory): return ActionResult( False, f"Expected: {directory} to be a directory to upload to the cloud.", ) args = ["cloud", "push"] args.extend(["--directory", directory]) args.extend(["--workspace", workspace_id]) args.extend(["--robot", robot_id]) args = self._add_config_to_args(args) error_action_result = self._add_account_to_args(args) if error_action_result is not None: return error_action_result ret = self._run_rcc(args, mutex_name=RCC_CLOUD_ROBOT_MUTEX_NAME) return ret
def mock_run_rcc(self, args, *sargs, **kwargs): if args[:3] == ["cloud", "new", "--workspace"]: return ActionResult( success=True, message=None, result="Created new robot named 'New package 1597082853.2224553' with identity 453.\n", ) if args[:3] == ["cloud", "push", "--directory"]: return ActionResult(success=True, message=None, result="OK.\n") raise AssertionError(f"Unexpected args: {args}")
def cloud_create_robot(self, workspace_id: str, robot_name: str) -> ActionResult[str]: from robocorp_ls_core.subprocess_wrapper import subprocess args = ["cloud", "new"] args.extend(["--workspace", workspace_id]) args.extend(["--robot", robot_name]) args = self._add_config_to_args(args) error_action_result = self._add_account_to_args(args) if error_action_result is not None: return error_action_result ret = self._run_rcc(args, mutex_name=RCC_CLOUD_ROBOT_MUTEX_NAME, stderr=subprocess.STDOUT) if not ret.success: return ret try: # Note: result is the package id. stdout = ret.result if not stdout: return ActionResult( False, f"No process stdout when creating new cloud package.") stdout = stdout.strip() # stdout is something as: # Created new robot named 'New package' with identity 1414. if not stdout.lower().startswith("created new"): return ActionResult( False, f'Expected output to start with "Created new". Found: {stdout}', ) if stdout.endswith("."): stdout = stdout[:-1] package_id = stdout.split(" ")[-1] if not package_id: return ActionResult( False, f"Unable to extract package id from: {stdout}") except Exception as e: log.exception("Error creating new robot.") return ActionResult( False, f"Unable to extract robot id from: {stdout}. Error: {e}") return ActionResult(ret.success, None, package_id)
def get_template_names(self) -> ActionResult[List[str]]: result = self._run_rcc("robot initialize -l".split()) if not result.success: return ActionResult(success=False, message=result.message) output = result.result if output is None: return ActionResult(success=False, message="Output not available") templates = [] for line in output.splitlines(): if line.startswith("- "): template_name = line[2:].strip() templates.append(template_name) return ActionResult(success=True, message=None, result=sorted(templates))
def cloud_list_workspace_robots( self, workspace_id: str ) -> ActionResult[List[IRccRobotMetadata]]: import json ret: List[IRccRobotMetadata] = [] args = ["cloud", "workspace"] args.extend(("--workspace", workspace_id)) args = self._add_config_to_args(args) error_action_result = self._add_account_to_args(args) if error_action_result is not None: return error_action_result result = self._run_rcc( args, expect_ok=False, mutex_name=RCC_CLOUD_ROBOT_MUTEX_NAME ) if not result.success: return ActionResult(False, result.message) output = result.result if not output: return ActionResult( False, "Error listing cloud workspace robots (output not available)." ) try: workspace_info = json.loads(output) except Exception as e: log.exception(f"Error parsing json: {output}") return ActionResult( False, f"Error loading json obtained while listing cloud workspaces activities.\n{e}", ) if not isinstance(workspace_info, dict): log.critical(f"Expected dict as top-level from json: {output}") msg = f"Unexpected type of cloud workspace activity json (expected dict, found: {type(workspace_info)}" return ActionResult(False, msg) for activity_info in workspace_info.get("activities", []): ret.append( RccRobotMetadata( robot_id=activity_info["id"], robot_name=activity_info["name"] ) ) return ActionResult(True, None, ret)
def custom_handler(args, *sargs, **kwargs): if args[:3] == ["cloud", "workspace", "--workspace"]: # List packages for workspace 1 return ActionResult( success=False, message="""{"error":{"code":"WORKSPACE_TREE_NOT_FOUND","subCode":"","message":"workspace tree not found"}""", result=None, )
def mock_run_rcc(self, args, *sargs, **kwargs): from robocorp_code.rcc import ACCOUNT_NAME if args[:5] == [ "config", "credentials", "--account", ACCOUNT_NAME, "--delete" ]: return ActionResult(success=True, message=None, result="OK.\n") raise AssertionError(f"Unexpected args: {args}")
def get_template_names(self) -> ActionResult[List[str]]: result = self._run_rcc("robot initialize -l".split()) if not result.success: return ActionResult(success=False, message=result.message) output = result.result if output is None: return ActionResult(success=False, message="Output not available") templates = set() for line in output.splitlines(): if line.startswith("- "): template_name = line[2:].strip() templates.add(template_name) ret: List[str] = [] for key, description in self._TEMPLATES.items(): if key in templates: ret.append(description) return ActionResult(success=True, message=None, result=ret)
def mock_run_rcc(self, args, *sargs, **kwargs): if args[:5] == [ "config", "credentials", "--account", "--robocorp-code", "--delete", ]: return ActionResult(success=True, message=None, result="OK.\n") raise AssertionError(f"Unexpected args: {args}")
def cloud_list_workspaces(self) -> ActionResult[List[IRccWorkspace]]: import json ret: List[IRccWorkspace] = [] args = ["cloud", "workspace"] args = self._add_config_to_args(args) error_action_result = self._add_account_to_args(args) if error_action_result is not None: return error_action_result result = self._run_rcc( args, expect_ok=False, mutex_name=RCC_CLOUD_ROBOT_MUTEX_NAME ) if not result.success: return ActionResult(False, result.message) output = result.result if not output: return ActionResult( False, "Error listing cloud workspaces (output not available)." ) try: lst = json.loads(output) except Exception as e: log.exception(f"Error parsing json: {output}") return ActionResult( False, f"Error loading json obtained while listing cloud workspaces.\n{e}", ) for workspace_info in lst: ret.append( RccWorkspace( workspace_id=workspace_info["id"], workspace_name=workspace_info["name"], ) ) return ActionResult(True, None, ret)
def _add_account_to_args(self, args: List[str]) -> Optional[ActionResult]: """ Adds the account to the args. Returns an error ActionResult if unable to get a valid account. """ account_info = self._last_verified_account_info if account_info is None: account_info = self.get_valid_account_info() if account_info is None: return ActionResult(False, "Unable to get valid account for action.") args.append("--account") args.append(account_info.account) return None
def mock_run_rcc_default(self, args, *sargs, **kwargs) -> ActionResult: import json import copy if args[:4] == ["cloud", "workspace", "--workspace", "workspace_id_1"]: # List packages for workspace 1 return ActionResult(success=True, message=None, result=json.dumps(self._package_info_ws_1)) if args[:4] == ["cloud", "workspace", "--workspace", "workspace_id_2"]: # List packages for workspace 2 return ActionResult(success=True, message=None, result=json.dumps(_PACKAGE_INFO_WS_2)) if args[:3] == ["cloud", "workspace", "--config"]: # List workspaces workspace_info = _WS_INFO return ActionResult(success=True, message=None, result=json.dumps(workspace_info)) if args[:3] == ["cloud", "push", "--directory"]: if args[4:8] == [ "--workspace", "workspace_id_1", "--robot", "2323" ]: return ActionResult(success=True) if args[4:8] == [ "--workspace", "workspace_id_1", "--robot", "453" ]: return ActionResult(success=True) if args[:5] == [ "cloud", "new", "--workspace", "workspace_id_1", "--robot" ]: # Submit a new package to ws 1 cp = copy.deepcopy(self._package_info_ws_1) cp["activities"].append({"id": "2323", "name": args[5]}) self._package_info_ws_1 = cp return ActionResult( success=True, message=None, result="Created new robot named {args[5]} with identity 2323.", ) raise AssertionError(f"Unexpected args: {args}")
def mock_run_rcc_default(self, args, *sargs, **kwargs) -> ActionResult: import json import copy if self.custom_handler is not None: ret = self.custom_handler(args, *sargs, **kwargs) if ret is not None: return ret if args[:4] == ["cloud", "workspace", "--workspace", "workspace_id_1"]: # List packages for workspace 1 return ActionResult( success=True, message=None, result=json.dumps(self._package_info_ws_1) ) if args[:4] == ["cloud", "workspace", "--workspace", "workspace_id_2"]: # List packages for workspace 2 return ActionResult( success=True, message=None, result=json.dumps(_PACKAGE_INFO_WS_2) ) if args[:3] == ["cloud", "workspace", "--config"]: # List workspaces workspace_info = _WS_INFO return ActionResult( success=True, message=None, result=json.dumps(workspace_info) ) if args[:3] == ["cloud", "push", "--directory"]: if args[4:8] == ["--workspace", "workspace_id_1", "--robot", "2323"]: return ActionResult(success=True) if args[4:8] == ["--workspace", "workspace_id_1", "--robot", "453"]: return ActionResult(success=True) if args[:5] == ["cloud", "new", "--workspace", "workspace_id_1", "--robot"]: # Submit a new package to ws 1 cp = copy.deepcopy(self._package_info_ws_1) cp["activities"].append({"id": "2323", "name": args[5]}) self._package_info_ws_1 = cp return ActionResult( success=True, message=None, result="Created new robot named {args[5]} with identity 2323.", ) if args[:4] == ["config", "credentials", "-j", "--verified"]: return ActionResult( success=True, message=None, result=json.dumps( [ { "account": "robocorp-code", "identifier": "001", "endpoint": "https://endpoint.foo.bar", "secret": "123...", "verified": 1605525807, } ] ), ) raise AssertionError(f"Unexpected args: {args}")
def check_conda_installed(self, timeout=None) -> ActionResult[str]: # With mamba this is not needed anymore. # Note: api kept just for backward compatibility. return ActionResult(success=True, message=None, result="OK.")
def _run_rcc( self, args: List[str], timeout: float = 30, error_msg: str = "", mutex_name=None, cwd: Optional[str] = None, log_errors=True, stderr=Sentinel.SENTINEL, ) -> ActionResult[str]: """ Returns an ActionResult where the result is the stdout of the executed command. :param log_errors: If false, errors won't be logged (i.e.: should be false when errors are expected). """ from robocorp_ls_core.basic import build_subprocess_kwargs from subprocess import check_output from robocorp_ls_core.subprocess_wrapper import subprocess if stderr is Sentinel.SENTINEL: stderr = subprocess.PIPE rcc_location = self.get_rcc_location() env = os.environ.copy() env.pop("PYTHONPATH", "") env.pop("PYTHONHOME", "") env.pop("VIRTUAL_ENV", "") env["PYTHONIOENCODING"] = "utf-8" env["PYTHONUNBUFFERED"] = "1" robocorp_home = self._get_robocorp_home() if robocorp_home: env["ROBOCORP_HOME"] = robocorp_home kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=stderr) args = [rcc_location] + args + ["--controller", "RobocorpCode"] cmdline = " ".join([str(x) for x in args]) try: if mutex_name: from robocorp_ls_core.system_mutex import timed_acquire_mutex else: timed_acquire_mutex = NULL with timed_acquire_mutex(mutex_name, timeout=15): boutput: bytes = check_output(args, timeout=timeout, **kwargs) except CalledProcessError as e: stdout = as_str(e.stdout) stderr = as_str(e.stderr) msg = f"Error running: {cmdline}.\nROBOCORP_HOME: {robocorp_home}\n\nStdout: {stdout}\nStderr: {stderr}" if log_errors: log.exception(msg) if not error_msg: return ActionResult(success=False, message=msg) else: additional_info = [error_msg] if stdout or stderr: if stdout and stderr: additional_info.append("\nDetails: ") additional_info.append("\nStdout") additional_info.append(stdout) additional_info.append("\nStderr") additional_info.append(stderr) elif stdout: additional_info.append("\nDetails: ") additional_info.append(stdout) elif stderr: additional_info.append("\nDetails: ") additional_info.append(stderr) return ActionResult(success=False, message="".join(additional_info)) except Exception: msg = f"Error running: {args}" log.exception(msg) return ActionResult(success=False, message=msg) output = boutput.decode("utf-8", "replace") log.debug("Output from: %s:\n%s", cmdline, output) return ActionResult(success=True, message=None, result=output)
def _run_rcc( self, args: List[str], timeout: float = 30, expect_ok=True, error_msg: str = "", mutex_name=None, cwd: Optional[str] = None, ) -> ActionResult[str]: """ Returns an ActionResult where the result is the stdout of the executed command. """ from robocorp_ls_core.basic import build_subprocess_kwargs from subprocess import check_output from robocorp_ls_core.subprocess_wrapper import subprocess rcc_location = self.get_rcc_location() env = os.environ.copy() env.pop("PYTHONPATH", "") env.pop("PYTHONHOME", "") env.pop("VIRTUAL_ENV", "") env["PYTHONIOENCODING"] = "utf-8" env["PYTHONUNBUFFERED"] = "1" kwargs: dict = build_subprocess_kwargs(cwd, env, stderr=subprocess.PIPE) args = [rcc_location] + args cmdline = " ".join([str(x) for x in args]) try: if mutex_name: from robocorp_ls_core.system_mutex import timed_acquire_mutex else: timed_acquire_mutex = NULL with timed_acquire_mutex(mutex_name, timeout=15): boutput: bytes = check_output(args, timeout=timeout, **kwargs) except CalledProcessError as e: stdout = as_str(e.stdout) stderr = as_str(e.stderr) msg = f"Error running: {cmdline}.\nStdout: {stdout}\nStderr: {stderr}" log.exception(msg) if not error_msg: return ActionResult(success=False, message=msg) else: additional_info = [error_msg] if stdout or stderr: if stdout and stderr: additional_info.append("\nDetails: ") additional_info.append("\nStdout") additional_info.append(stdout) additional_info.append("\nStderr") additional_info.append(stderr) elif stdout: additional_info.append("\nDetails: ") additional_info.append(stdout) elif stderr: additional_info.append("\nDetails: ") additional_info.append(stderr) return ActionResult(success=False, message="".join(additional_info)) except Exception: msg = f"Error running: {args}" log.exception(msg) return ActionResult(success=False, message=msg) output = boutput.decode("utf-8", "replace") log.debug(f"Output from: {cmdline}:\n{output}") if expect_ok: if "OK." in output: return ActionResult(success=True, message=None, result=output) else: return ActionResult(success=True, message=None, result=output) return ActionResult( success=False, message="OK. not found in message", result=output )