summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/airbag.py17
-rw-r--r--python/vyos/template.py10
-rw-r--r--python/vyos/util.py87
3 files changed, 88 insertions, 26 deletions
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
index 6698aa404..b7838d8a2 100644
--- a/python/vyos/airbag.py
+++ b/python/vyos/airbag.py
@@ -26,6 +26,17 @@ from vyos.version import get_full_version_data
DISABLE = False
+_noteworthy = []
+
+def noteworthy(msg):
+ """
+ noteworthy can be use to take note things which we may not want to
+ report to the user may but be worth including in bug report
+ if something goes wrong later on
+ """
+ _noteworthy.append(msg)
+
+
# emulate a file object
class _IO(object):
def __init__(self, std, log):
@@ -58,11 +69,16 @@ def bug_report(dtype, value, trace):
information = get_full_version_data()
trace = '\n'.join(format_exception(dtype, value, trace)).replace('\n\n','\n')
+ note = ''
+ if _noteworthy:
+ note = 'noteworthy:\n'
+ note += '\n'.join(_noteworthy)
information.update({
'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'trace': trace,
'instructions': COMMUNITY if 'rolling' in get_version() else SUPPORTED,
+ 'note': note,
})
sys.stdout.write(INTRO.format(**information))
@@ -145,6 +161,7 @@ Hardware S/N: {hardware_serial}
Hardware UUID: {hardware_uuid}
{trace}
+{note}
"""
INTRO = """\
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 6c73ce753..e4b253ed3 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -19,6 +19,7 @@ from jinja2 import Environment
from jinja2 import FileSystemLoader
from vyos.defaults import directories
+from vyos.util import chmod, chown, makedir
# reuse the same Environment to improve performance
@@ -32,7 +33,7 @@ _templates_mem = {
}
-def render(destination, template, content, trim_blocks=False, formater=None):
+def render(destination, template, content, trim_blocks=False, formater=None, permission=None, user=None, group=None):
"""
render a template from the template directory, it will raise on any errors
destination: the file where the rendered template must be saved
@@ -46,6 +47,10 @@ def render(destination, template, content, trim_blocks=False, formater=None):
(recovering the load time and overhead caused by having the file out of the code)
"""
+ # Create the directory if it does not exists
+ folder = os.path.dirname(destination)
+ makedir(folder, user, group)
+
# Setup a renderer for the given template
# This is cached and re-used for performance
if template not in _templates_mem[trim_blocks]:
@@ -63,3 +68,6 @@ def render(destination, template, content, trim_blocks=False, formater=None):
# Write client config file
with open(destination, 'w') as f:
f.write(content)
+
+ chmod(destination, permission)
+ chown(destination, user, group)
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 4340332d3..307decc87 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -14,6 +14,7 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import os
+import sys
#
# NOTE: Do not import full classes here, move your import to the function
@@ -25,7 +26,7 @@ import os
# which all have slighty different behaviour
from subprocess import Popen, PIPE, STDOUT, DEVNULL
def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
- stdout=PIPE, stderr=None, decode=None):
+ stdout=PIPE, stderr=PIPE, decode='utf-8'):
"""
popen is a wrapper helper aound subprocess.Popen
with it default setting it will return a tuple (out, err)
@@ -48,12 +49,14 @@ def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
- STDOUT, send the data to be merged with stdout
- DEVNULL, discard the output
decode: specify the expected text encoding (utf-8, ascii, ...)
+ the default is explicitely utf-8 which is python's own default
usage:
to get both stdout, and stderr: popen('command', stdout=PIPE, stderr=STDOUT)
to discard stdout and get stderr: popen('command', stdout=DEVNUL, stderr=PIPE)
"""
from vyos import debug
+ from vyos import airbag
# log if the flag is set, otherwise log if command is set
if not debug.enabled(flag):
flag = 'command'
@@ -77,27 +80,39 @@ def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
stdin=stdin, stdout=stdout, stderr=stderr,
env=env, shell=use_shell,
)
- tmp = p.communicate(input, timeout)
- out1 = b''
- out2 = b''
+
+ pipe = p.communicate(input, timeout)
+
+ pipe_out = b''
if stdout == PIPE:
- out1 = tmp[0]
+ pipe_out = pipe[0]
+
+ pipe_err = b''
if stderr == PIPE:
- out2 += tmp[1]
- decoded1 = out1.decode(decode) if decode else out1.decode()
- decoded2 = out2.decode(decode) if decode else out2.decode()
- decoded1 = decoded1.replace('\r\n', '\n').strip()
- decoded2 = decoded2.replace('\r\n', '\n').strip()
- nl = '\n' if decoded1 and decoded2 else ''
- decoded = decoded1 + nl + decoded2
- if decoded:
- ret_msg = f"returned:\n{decoded}"
- debug.message(ret_msg, flag)
- return decoded, p.returncode
+ pipe_err = pipe[1]
+
+ str_out = pipe_out.decode(decode).replace('\r\n', '\n').strip()
+ str_err = pipe_err.decode(decode).replace('\r\n', '\n').strip()
+
+ out_msg = f"returned (out):\n{str_out}"
+ if str_out:
+ debug.message(out_msg, flag)
+
+ if str_err:
+ err_msg = f"returned (err):\n{str_err}"
+ # this message will also be send to syslog via airbag
+ debug.message(err_msg, flag, destination=sys.stderr)
+
+ # should something go wrong, report this too via airbag
+ airbag.noteworthy(cmd_msg)
+ airbag.noteworthy(out_msg)
+ airbag.noteworthy(err_msg)
+
+ return str_out, p.returncode
def run(command, flag='', shell=None, input=None, timeout=None, env=None,
- stdout=DEVNULL, stderr=None, decode=None):
+ stdout=DEVNULL, stderr=PIPE, decode='utf-8'):
"""
A wrapper around vyos.util.popen, which discard the stdout and
will return the error code of a command
@@ -113,14 +128,15 @@ def run(command, flag='', shell=None, input=None, timeout=None, env=None,
def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
- stdout=PIPE, stderr=None, decode=None,
- raising=None, message=''):
+ stdout=PIPE, stderr=PIPE, decode='utf-8',
+ raising=None, message='', expect=[0]):
"""
A wrapper around vyos.util.popen, which returns the stdout and
will raise the error code of a command
raising: specify which call should be used when raising (default is OSError)
the class should only require a string as parameter
+ expect: a list of error codes to consider as normal
"""
decoded, code = popen(
command, flag,
@@ -129,7 +145,7 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
env=env, shell=shell,
decode=decode,
)
- if code != 0:
+ if code not in expect:
feedback = message + '\n' if message else ''
feedback += f'failed to run command: {command}\n'
feedback += f'returned: {decoded}\n'
@@ -143,7 +159,7 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
def call(command, flag='', shell=None, input=None, timeout=None, env=None,
- stdout=PIPE, stderr=None, decode=None):
+ stdout=PIPE, stderr=PIPE, decode='utf-8'):
"""
A wrapper around vyos.util.popen, which print the stdout and
will return the error code of a command
@@ -197,10 +213,24 @@ def chown(path, user, group):
from pwd import getpwnam
from grp import getgrnam
- if os.path.exists(path):
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
- os.chown(path, uid, gid)
+ if user is None or group is None:
+ return False
+
+ if not os.path.exists(path):
+ return False
+
+ uid = getpwnam(user).pw_uid
+ gid = getgrnam(group).gr_gid
+ os.chown(path, uid, gid)
+ return True
+
+
+def chmod(path, bitmask):
+ if not os.path.exists(path):
+ return
+ if bitmask is None:
+ return
+ os.chmod(path, bitmask)
def chmod_600(path):
@@ -231,6 +261,13 @@ def chmod_755(path):
os.chmod(path, bitmask)
+def makedir(path, user=None, group=None):
+ if not os.path.exists(path):
+ return
+ os.mkdir(path)
+ chown(path, user, group)
+
+
def colon_separated_to_dict(data_string, uniquekeys=False):
""" Converts a string containing newline-separated entries
of colon-separated key-value pairs into a dict.