src: add support for direct command execution

Update live shell run mode for the new REST API interface.
Evaluate the "inline" field to diferentiate between execution of
script in /opt/opengnsys/shell/ and a cmd execution.

Remove usage of echo argument of the API REST.

Update Windows and Linux mode for direct command execution.
Set OutputEncoding environment variable to 'utf-8' in Windows to
unify the encoding of stdout for the invoked programs.

Decode stdout to utf-8-sig to remove potential BOM.

While at this, remove strange legacy ;|\n\r terminator.
master v1.3.2-25
Alejandro Sirgo Rica 2024-11-26 11:50:52 +01:00
parent a36c4daa23
commit e4be5c34eb
5 changed files with 72 additions and 62 deletions

View File

@ -7,9 +7,9 @@
# (at your option) any later version. # (at your option) any later version.
import os import os
import shlex
import psutil import psutil
import subprocess import subprocess
from subprocess import CalledProcessError
from src.log import OgError from src.log import OgError
from src.ogRest import ThreadState from src.ogRest import ThreadState
@ -30,20 +30,22 @@ class OgLinuxOperations:
def shellrun(self, request, ogRest): def shellrun(self, request, ogRest):
cmd = request.getrun() cmd = request.getrun()
is_inline = request.get_inline()
if not is_inline:
raise OgError("Only inline mode is supported on Linux")
try: try:
result = subprocess.run(cmd, ogRest.proc = subprocess.Popen(
shell=True, cmd,
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
capture_output=True, shell=True)
text=True, (output, error) = ogRest.proc.communicate()
check=True) except (OSError, subprocess.SubprocessError) as e:
except CalledProcessError as error: raise OgError(f'Error when running "shell run" subprocess: {e}') from e
if error.stderr:
return error.stderr output = output.decode('utf-8-sig', errors='replace')
if error.stdout: return (ogRest.proc.returncode, cmd, output)
return error.stdout
return "{Non zero exit code and empty output}"
return result.stdout
def session(self, request, ogRest): def session(self, request, ogRest):
raise OgError('Function not implemented') raise OgError('Function not implemented')

View File

@ -38,8 +38,6 @@ from src.utils.hw_inventory import get_hardware_inventory, legacy_list_hardware_
from src.log import OgError from src.log import OgError
OG_SHELL = '/bin/bash'
class OgLiveOperations: class OgLiveOperations:
def __init__(self, config): def __init__(self, config):
self._url = config['opengnsys']['url'] self._url = config['opengnsys']['url']
@ -381,32 +379,38 @@ class OgLiveOperations:
def shellrun(self, request, ogRest): def shellrun(self, request, ogRest):
cmd = request.getrun() cmd = request.getrun()
cmds = cmd.split(";|\n\r") is_inline = request.get_inline()
self._restartBrowser(self._url_log) self._restartBrowser(self._url_log)
shell_path = '/opt/opengnsys/shell/' if is_inline:
cmds = cmd
else:
cmds = shlex.split(cmd)
shell_path = '/opt/opengnsys/shell/'
restricted_mode = False try:
shell_path_files = os.listdir(shell_path)
except OSError as e:
raise OgError(f'Error accessing {shell_path}: {e}') from e
for file_name in os.listdir(shell_path): for file_name in shell_path_files:
file_path = os.path.join(shell_path, file_name) file_path = os.path.join(shell_path, file_name)
if cmds[0] == file_name: if cmds[0] == file_name:
cmds[0] = file_path cmds[0] = file_path
restricted_mode = True cmd = " ".join(cmds)
break break
else:
raise OgError(f'Script {cmds[0]} not found in {shell_path}')
try: try:
if restricted_mode: ogRest.proc = subprocess.Popen(
ogRest.proc = subprocess.Popen(cmds, stdout=subprocess.PIPE) cmds,
else: stdout=subprocess.PIPE,
ogRest.proc = subprocess.Popen(cmds, shell=is_inline)
stdout=subprocess.PIPE,
shell=True,
executable=OG_SHELL)
(output, error) = ogRest.proc.communicate() (output, error) = ogRest.proc.communicate()
except OSError as e: except (OSError, subprocess.SubprocessError) as e:
raise OgError(f'Error when running "shell run" subprocess: {e}') from e raise OgError(f'Error when running "shell run" subprocess: {e}') from e
if ogRest.proc.returncode != 0: if ogRest.proc.returncode != 0:
@ -416,7 +420,8 @@ class OgLiveOperations:
self.refresh(ogRest) self.refresh(ogRest)
return (ogRest.proc.returncode, " ".join(cmds), output.decode('utf-8')) output = output.decode('utf-8-sig', errors='replace')
return (ogRest.proc.returncode, cmd, output)
def session(self, request, ogRest): def session(self, request, ogRest):
disk = request.getDisk() disk = request.getDisk()

View File

@ -97,16 +97,12 @@ class ogThread():
ogRest.send_internal_server_error(client, exc=e) ogRest.send_internal_server_error(client, exc=e)
return return
if request.getEcho(): json_body = jsonBody()
json_body = jsonBody() json_body.add_element('cmd', cmd)
json_body.add_element('cmd', cmd) json_body.add_element('out', shellout)
json_body.add_element('out', shellout) json_body.add_element('retcode', retcode)
json_body.add_element('retcode', retcode) response = restResponse(ogResponses.OK, json_body, seq=client.seq)
response = restResponse(ogResponses.OK, json_body, seq=client.seq) client.send(response.get())
client.send(response.get())
else:
response = restResponse(ogResponses.OK, seq=client.seq)
client.send(response.get())
ogRest.state = ThreadState.IDLE ogRest.state = ThreadState.IDLE

View File

@ -33,7 +33,7 @@ class restRequest:
self.type = None self.type = None
self.profile = None self.profile = None
self.id = None self.id = None
self.echo = None self.inline = None
self.code = None self.code = None
self.seq = None self.seq = None
self.backup = None self.backup = None
@ -72,7 +72,7 @@ class restRequest:
if "run" in json_param: if "run" in json_param:
self.run = json_param["run"] self.run = json_param["run"]
try: try:
self.echo = json_param["echo"] self.inline = json_param["inline"]
except: except:
pass pass
@ -160,8 +160,8 @@ class restRequest:
def getId(self): def getId(self):
return self.id return self.id
def getEcho(self): def get_inline(self):
return self.echo return self.inline
def getCode(self): def getCode(self):
return self.code return self.code

View File

@ -7,10 +7,10 @@
# (at your option) any later version. # (at your option) any later version.
import os import os
import locale
import ctypes import ctypes
import psutil import psutil
import subprocess import subprocess
from subprocess import CalledProcessError
import multiprocessing as mp import multiprocessing as mp
from multiprocessing import Process, freeze_support from multiprocessing import Process, freeze_support
from src.log import OgError from src.log import OgError
@ -84,20 +84,27 @@ class OgWindowsOperations:
def shellrun(self, request, ogRest): def shellrun(self, request, ogRest):
cmd = request.getrun() cmd = request.getrun()
is_inline = request.get_inline()
if not is_inline:
raise OgError("Only inline mode is supported on Windows")
env = os.environ.copy()
env['OutputEncoding'] = 'utf8'
try: try:
result = subprocess.run(cmd, ogRest.proc = subprocess.Popen(
shell=True, cmd,
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
capture_output=True, shell=True,
text=True, env=env,
check=True) )
except CalledProcessError as error: output, error = ogRest.proc.communicate()
if error.stderr: except (OSError, subprocess.SubprocessError) as e:
return error.stderr raise OgError(f'Error when running "shell run" subprocess: {e}') from e
if error.stdout:
return error.stdout output = output.decode('utf-8-sig', errors='replace')
return "{Non zero exit code and empty output}" return (ogRest.proc.returncode, cmd, output)
return (result.returncode, cmd, result.stdout)
def session(self, request, ogRest): def session(self, request, ogRest):
raise OgError('Function not implemented') raise OgError('Function not implemented')