Create wrapper class to run commands via subprocess
This commit creates a class to wrap the main subprocess commands used by USM, that are 'run', 'check_output' and 'check_call' so that their returned information is logged into a json file. The purpose of this file is to enable each deployment history to be recovered in an easier way on the future. The json files are stored inside directories named under the corresponding release, and have a file named under the deployment stage in which the object was instantiated, e.g.: /opt/software/summary/starlingx_24.03.0/deploy_precheck.json These files can grow incrementally in case multiple commands are executed under the same deployment stage. The wrapper functions have the same name as the ones from the 'subprocess' library and behave exactly the same way. The subprocess functions used on the code currenly will be replaced by a follow-up commit. Test Plan PASS: manually replace subprocess functions on USM code, run the respective commands and verify: - Command is executed successfully - Output and behavior is maintained - The json file is created with the expected directory, filename and content Story: 2010676 Task: 48955 Change-Id: Iccf1aef1b0cc064399163eeb58c23fa065a6dab5 Signed-off-by: Heitor Matsui <heitorvieira.matsui@windriver.com>
This commit is contained in:
parent
e6744eb9f5
commit
71dfcf8469
|
@ -111,3 +111,6 @@ LICENSE_FILE = "/etc/platform/.license"
|
|||
VERIFY_LICENSE_BINARY = "/usr/bin/verify-license"
|
||||
|
||||
SOFTWARE_JSON_FILE = "/opt/software/software.json"
|
||||
|
||||
WORKER_SUMMARY_DIR = "%s/summary" % SOFTWARE_STORAGE_DIR
|
||||
WORKER_DATETIME_FORMAT = "%Y%m%dT%H%M%S%f"
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
"""
|
||||
Copyright (c) 2023 Wind River Systems, Inc.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
import software.constants as constants
|
||||
|
||||
|
||||
class SoftwareWorker(object):
|
||||
"""This class wraps the subprocess commands used by USM
|
||||
modules to run a command with parameters and write its
|
||||
return code, stdout, stderr and other useful information into
|
||||
a structured json file that can be later recovered to create
|
||||
a deployment summary report.
|
||||
"""
|
||||
def __init__(self, release, stage):
|
||||
"""SoftwareWorker constructor
|
||||
|
||||
:param release: target release name, used to define
|
||||
the directory in which json files will be created
|
||||
:param stage: deployment stage which the commands
|
||||
are being executed, used to define the json filename
|
||||
"""
|
||||
self._release = release
|
||||
self._stage = stage
|
||||
self._directory = os.path.join(constants.WORKER_SUMMARY_DIR, self._release)
|
||||
self._filename = os.path.join(self._directory, self._stage) + ".json"
|
||||
os.makedirs(self._directory, exist_ok=True)
|
||||
|
||||
@staticmethod
|
||||
def _convert_string(_str):
|
||||
"""Convert a byte-string into a text string
|
||||
|
||||
:param _str: string to be converted
|
||||
:returns: text string
|
||||
"""
|
||||
if not _str:
|
||||
return ""
|
||||
if isinstance(_str, bytes):
|
||||
return _str.decode("utf-8")
|
||||
return _str
|
||||
|
||||
def _read_file(self):
|
||||
"""Reads the file and returns its content in a dictionary.
|
||||
|
||||
:returns: dictionary loaded with content from json file
|
||||
"""
|
||||
try:
|
||||
with open(self._filename, "r") as f:
|
||||
return json.loads(f.read())
|
||||
except (FileNotFoundError, json.decoder.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
def _write_file(self, command, rc, stdout, stderr):
|
||||
"""Writes the command in a structured format in the file.
|
||||
|
||||
:param command: command that was run via subprocess
|
||||
:param rc: command return code
|
||||
:param stdout: standard output returned by the command
|
||||
:param stderr: standard error returned by the command
|
||||
"""
|
||||
commands = self._read_file()
|
||||
if not isinstance(command, list):
|
||||
command = [command]
|
||||
with open(self._filename, "w") as f:
|
||||
commands.update({
|
||||
datetime.strftime(datetime.utcnow(), constants.WORKER_DATETIME_FORMAT): {
|
||||
"command": " ".join(command),
|
||||
"rc": rc,
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
}
|
||||
})
|
||||
f.write(json.dumps(commands))
|
||||
|
||||
def run(self, cmd, **kwargs):
|
||||
"""Wraps 'run' function from 'subprocess' library and writes captured
|
||||
rc, stdout and stderr from the executed command to a json file.
|
||||
|
||||
:param cmd: list or str with the command and parameters to execute
|
||||
:param kwargs: extra parameters passed to native subprocess 'run' function
|
||||
:returns: instance of CompletedProcess or CalledProcessError exception
|
||||
"""
|
||||
stdout = kwargs.get("stdout", None)
|
||||
stderr = kwargs.get("stderr", None)
|
||||
capture_output = kwargs.get("capture_output", False)
|
||||
shell = kwargs.get("shell", False)
|
||||
check = kwargs.get("check", False)
|
||||
text = kwargs.get("text", None)
|
||||
|
||||
try:
|
||||
if "capture_output" in kwargs:
|
||||
ret = subprocess.run(cmd, shell=shell, check=check,
|
||||
capture_output=capture_output, text=text)
|
||||
else:
|
||||
ret = subprocess.run(cmd, shell=shell, check=check,
|
||||
stdout=stdout, stderr=stderr, text=text)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
ret = exc
|
||||
|
||||
ret_stdout_text = self._convert_string(ret.stdout)
|
||||
ret_stderr_text = self._convert_string(ret.stderr)
|
||||
self._write_file(cmd, ret.returncode, ret_stdout_text, ret_stderr_text)
|
||||
return ret
|
||||
|
||||
def check_output(self, cmd, **kwargs):
|
||||
"""Wraps 'check_output' function from 'subprocess' library and writes
|
||||
captured rc, stdout and stderr from the executed command to a json file.
|
||||
Like the original 'subprocess' function, will return the command output
|
||||
if the command is successful and raise an exception if it fails.
|
||||
|
||||
:param cmd: list or str with the command and parameters to execute
|
||||
:param kwargs: extra parameters passed to native subprocess 'run' function
|
||||
:returns: subprocess.CompletedProcess.stdout
|
||||
"""
|
||||
kwargs.update({"check": True, "stdout": subprocess.PIPE,
|
||||
"stderr": kwargs.get("stderr", None)})
|
||||
ret = self.run(cmd, **kwargs)
|
||||
if ret.returncode != 0:
|
||||
raise subprocess.CalledProcessError(ret.returncode, cmd)
|
||||
return ret.stdout
|
||||
|
||||
def check_call(self, cmd, **kwargs):
|
||||
"""Wraps 'check_call' function from 'subprocess' library and writes
|
||||
captured rc, stdout='' and stderr='' from the executed command to a
|
||||
json file. Like the original 'subprocess' function, will raise an
|
||||
exception if the command fails.
|
||||
|
||||
:param cmd: list or str with the command and parameters to execute
|
||||
:param kwargs: extra parameters passed to native subprocess 'run' function
|
||||
"""
|
||||
kwargs.update({"check": True, "stdout": subprocess.DEVNULL,
|
||||
"stderr": subprocess.DEVNULL})
|
||||
ret = self.run(cmd, **kwargs)
|
||||
if ret.returncode != 0:
|
||||
raise subprocess.CalledProcessError(ret.returncode, cmd)
|
Loading…
Reference in New Issue