diff --git a/Sources/pyOpenRPA/Agent/__Agent__.py b/Sources/pyOpenRPA/Agent/__Agent__.py index 9ac93c19..68e3505c 100644 --- a/Sources/pyOpenRPA/Agent/__Agent__.py +++ b/Sources/pyOpenRPA/Agent/__Agent__.py @@ -87,7 +87,7 @@ def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=N return lFileDataStr # Send CMD to OS. Result return to log + Orchestrator by the A2O connection -def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251"): +def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251", inCaptureBool = True): """ Execute CMD on the Agent daemonic process @@ -95,18 +95,18 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inGSettings: Agent global settings dict :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True - !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: """ lResultStr = "" # Subdef to listen OS result - def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None): + def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None, inCaptureBool = True): lL = inGSettings.get("Logger",None) if type(inGSettings) is dict else None lResultStr = "" lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() lCMDProcess = None - if inSendOutputToOrchestratorLogsBool == True: + if inCaptureBool == True: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=None, stderr=None, @@ -114,12 +114,15 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = True lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been STARTED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) lMessageStr = f"{lOSCMDKeyStr}: {inCMDStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) while lListenBool: - if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + #if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + if inCaptureBool == True: # Capturing can be turned on! lOutputLineBytes = lCMDProcess.stdout.readline() if lOutputLineBytes == b"": lListenBool = False @@ -127,7 +130,8 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche if lStr.endswith("\n"): lStr = lStr[:-1] lMessageStr = f"{lOSCMDKeyStr}: {lStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) lResultStr+=lStr else: #Capturing is not turned on - wait until process will be closed lCMDProcessPoll = lCMDProcess.poll() @@ -137,15 +141,16 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = False lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been FINISHED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) return lResultStr # New call if inRunAsyncBool: - lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr }) + lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr, "inCaptureBool": inCaptureBool }) lThread.start() lResultStr="ActivityList has been started in async mode - no output is available here." else: - lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr) + lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr, inCaptureBool=inCaptureBool) #lCMDCode = "cmd /c " + inCMDStr #subprocess.Popen(lCMDCode) #lResultCMDRun = 1 # os.system(lCMDCode) diff --git a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py index 61e7e7d0..b503c5da 100644 --- a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py +++ b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -515,4 +515,9 @@ def Update(inGSettings): # Check "SchedulerDict": { "Schedule": schedule, # https://schedule.readthedocs.io/en/stable/examples.html if inGSettings.get("SchedulerDict",{}).get("Schedule",None) is None: inGSettings["SchedulerDict"]["Schedule"] = schedule - if lL: lL.warning(f"Backward compatibility (v1.2.4 to v1.2.7): Create new module schedule (schedule.readthedocs.io)") # Log about compatibility \ No newline at end of file + if lL: lL.warning(f"Backward compatibility (v1.2.4 to v1.2.7): Create new module schedule (schedule.readthedocs.io)") # Log about compatibility + # ManagersGitDict + if "ManagersGitDict" not in inGSettings: + inGSettings["ManagersGitDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ManagersGitDict") # Log about compatibility diff --git a/Sources/pyOpenRPA/Orchestrator/Managers/Git.py b/Sources/pyOpenRPA/Orchestrator/Managers/Git.py new file mode 100644 index 00000000..4089a764 --- /dev/null +++ b/Sources/pyOpenRPA/Orchestrator/Managers/Git.py @@ -0,0 +1,103 @@ +import os +from .. import __Orchestrator__ + +class Git(): + + mAgentHostNameStr = None + mAgentUserNameStr = None + mAbsPathStr = None + + def __init__(self, inAgentHostNameStr=None, inAgentUserNameStr=None, inGitPathStr=""): + """ + Init the Git repo instance. It helps to detect new changes in repo and auto restart services + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process. If None - works with Orc session + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process. If None - works with Orc session + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: + """ + lAbsPathUpperStr = os.path.abspath(inGitPathStr).upper() + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr) not in lGS["ManagersGitDict"]: + self.mAbsPathUpperStr = lAbsPathUpperStr + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + lGS["ManagersGitDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr)]=self + else: raise Exception(f"Managers.Git ({inAgentHostNameStr}, {inAgentUserNameStr}, {lAbsPathUpperStr}): Can't init the Git instance because it already inited in early") + + def __OSCMDShell__(self, inCMDStr): + """ + Detect the way of use and send the cmd + + :return: None is not exists + """ + if self.mAgentUserNameStr is not None and self.mAgentHostNameStr is not None: # Check if Agent specified + lActivityItemGUIDStr = __Orchestrator__.AgentOSCMD(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inCMDStr=inCMDStr,inRunAsyncBool=False,inSendOutputToOrchestratorLogsBool=False) + lCMDResultStr = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lActivityItemGUIDStr) + else: + lCMDResultStr = __Orchestrator__.OSCMD(inCMDStr=inCMDStr, inRunAsyncBool=False) + return lCMDResultStr + + def BranchRevGet(self, inBranchNameStr="HEAD"): + """ + Get the specified branch revision. Default return the current branch revision + + .. code-block:: python + lGit.BranchRevGet(inBranchNameStr="dev") # Get revision of the local dev branch + lGit.BranchRevGet(inBranchNameStr="remotes/origin/dev") # Get revision of the remotes dev branch + lGit.BranchRevGet(inBranchNameStr="HEAD") # Get revision of the current HEAD branch + lGit.BranchRevGet() # Equal to the call inBranchNameStr="HEAD" + + :param inBranchNameStr: The branch name where to get revision guid + :return: revision GUID + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git rev-parse {inBranchNameStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def Fetch(self): + """ + Get updates from the git server. + + .. code-block:: python + lGit.Fetch() # get updates from the server + + :return: None + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git fetch" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + +def GitExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> bool: + """ + Check if the Git instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inGitPathStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersGitDict"] + + +def GitGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> Git: + """ + Return the Git instance by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: Git instance (if exists) Else None + """ + lAbsPathUpperStr = os.path.abspath(inGitPathStr).upper() + return __Orchestrator__.GSettingsGet()["ManagersGitDict"].get((inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr),None) + + +def GitBranchRevGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str, inBranchNameStr: str="HEAD") -> str: + lGit = GitGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inGitPathStr=inGitPathStr) + if lGit is not None: return lGit.BranchRevGet(inBranchNameStr=inBranchNameStr) + +def GitFetch(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> None: + lGit = GitGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inGitPathStr=inGitPathStr) + if lGit is not None: lGit.Fetch() \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/Managers/__init__.py b/Sources/pyOpenRPA/Orchestrator/Managers/__init__.py index 9c455b1b..f705852f 100644 --- a/Sources/pyOpenRPA/Orchestrator/Managers/__init__.py +++ b/Sources/pyOpenRPA/Orchestrator/Managers/__init__.py @@ -1,2 +1,3 @@ from .ControlPanel import * -from .Process import * \ No newline at end of file +from .Process import * +from .Git import * \ No newline at end of file diff --git a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py index ddb3a3dd..7999b7fa 100644 --- a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py +++ b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py @@ -151,6 +151,7 @@ def __Create__(): ], }, "ManagersProcessDict":{}, # The key of the Process is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mProcessNameWOExeStr.upper()) + "ManagersGitDict":{}, # The key of the Git instance is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mAbsPathUpperStr.upper()) "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { diff --git a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index 28c3f533..a72ddb4c 100644 --- a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -124,7 +124,7 @@ def AgentActivityItemReturnGet(inGUIDStr, inCheckIntervalSecFloat = 0.5, inGSett else: raise Exception(f"__Orchestrator__.AgentActivityItemReturnGet !ATTENTION! Use this function only after Orchestrator initialization! Before orchestrator init exception will be raised.") -def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None): +def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None, inCaptureBool=True): """ Send CMD to OS thought the pyOpenRPA.Agent daemon. Result return to log + Orchestrator by the A2O connection @@ -135,13 +135,14 @@ def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOu :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list - "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr}, # Args dictionary + "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr, "inCaptureBool":inCaptureBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) }