From df231744b98202ec5fbcd236e795df7399747a0e Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 23 May 2019 07:38:54 -0500 Subject: T1397: Rewrite the config merge script Add vyos.config.show_config to show working configuration. Add vyos.remote.get_config_remote() for obtaining remote config files. --- python/vyos/config.py | 15 ++++++ python/vyos/remote.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 python/vyos/remote.py (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index bcf04225b..9a5125eb9 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -169,6 +169,21 @@ class Config(object): except VyOSError: return False + def show_config(self, path='', default=None): + """ + Args: + path (str): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + try: + out = self._run(self._make_command('showConfig', path)) + return out + except VyOSError: + return(default) + def is_multi(self, path): """ Args: diff --git a/python/vyos/remote.py b/python/vyos/remote.py new file mode 100644 index 000000000..372780c91 --- /dev/null +++ b/python/vyos/remote.py @@ -0,0 +1,133 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +import sys +import os +import re +import fileinput +import subprocess + + +def check_and_add_host_key(host_name): + """ + Filter host keys and prompt for adding key to known_hosts file, if + needed. + """ + known_hosts = '{}/.ssh/known_hosts'.format(os.getenv('HOME')) + + keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name) + + try: + host_key = subprocess.check_output(keyscan_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: + sys.exit("Can not get RSA host key") + + # libssh2 (jessie; stretch) does not recognize ec host keys, and curl + # will fail with error 51 if present in known_hosts file; limit to rsa. + usable_keys = False + offending_keys = [] + for line in fileinput.input(known_hosts, inplace=True): + if host_name in line and 'ssh-rsa' in line: + if line.split()[-1] != host_key.split()[-1]: + offending_keys.append(line) + continue + else: + usable_keys = True + if host_name in line and not 'ssh-rsa' in line: + continue + + sys.stdout.write(line) + + if usable_keys: + return + + if offending_keys: + print("Host key has changed!") + print("If you trust the host key fingerprint below, continue.") + + fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key) + try: + fingerprint = subprocess.check_output(fingerprint_cmd, shell=True, + stderr=subprocess.DEVNULL, + universal_newlines=True) + except subprocess.CalledProcessError as err: + sys.exit("Can not get RSA host key fingerprint.") + + print("RSA host key fingerprint is {}".format(fingerprint.split()[1])) + response = input("Do you trust this host? [y]/n ") + + if not response or response == 'y': + with open(known_hosts, 'a+') as f: + print("Adding {} to the list of known" + " hosts.".format(host_name)) + f.write(host_key) + else: + sys.exit("Host not trusted") + +def get_remote_config(remote_file): + """ Invoke curl to download remote (config) file. + + Args: + remote file URI: + scp://[:]@/ + sftp://[:]@/ + http:/// + https:/// + ftp://[:]@/ + tftp:/// + """ + request = dict.fromkeys(['protocol', 'host', 'file', 'user', 'passwd']) + protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + or_protocols = '|'.join(protocols) + + request_match = re.match(r'(' + or_protocols + r')://(.*?)(/.*)', + remote_file) + if request_match: + (request['protocol'], request['host'], + request['file']) = request_match.groups() + else: + print("Malformed URI") + sys.exit(1) + + user_match = re.search(r'(.*)@(.*)', request['host']) + if user_match: + request['user'] = user_match.groups()[0] + request['host'] = user_match.groups()[1] + passwd_match = re.search(r'(.*):(.*)', request['user']) + if passwd_match: + # Deprectated in RFC 3986, but maintain for backward compatability. + request['user'] = passwd_match.groups()[0] + request['passwd'] = passwd_match.groups()[1] + + remote_file = '{0}://{1}{2}'.format(request['protocol'], request['host'], request['file']) + + if request['protocol'] in ('scp', 'sftp'): + check_and_add_host_key(request['host']) + + if request['user'] and not request['passwd']: + curl_cmd = 'curl -# -u {0} {1}'.format(request['user'], remote_file) + else: + curl_cmd = 'curl -# {0}'.format(remote_file) + + config_file = None + try: + config_file = subprocess.check_output(curl_cmd, shell=True, + universal_newlines=True) + except subprocess.CalledProcessError as err: + print("Called process error: {}.".format(err)) + + return config_file -- cgit v1.2.3 From 456abc2aa4ae10981c2aec2d2e6d975ef30fb8d6 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 28 May 2019 14:39:39 -0500 Subject: T1397: Rewrite the config merge script Add the script vyos-merge-config.py to separate the merge function from the config load script and remove dependency on XorpConfigParser. --- python/vyos/defaults.py | 3 +- src/helpers/vyos-merge-config.py | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100755 src/helpers/vyos-merge-config.py (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 36185f16a..0603efc42 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -15,7 +15,8 @@ directories = { - "data": "/usr/share/vyos/" + "data": "/usr/share/vyos/", + "config": "/opt/vyatta/etc/config" } cfg_group = 'vyattacfg' diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py new file mode 100755 index 000000000..f0d5d1595 --- /dev/null +++ b/src/helpers/vyos-merge-config.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 + +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +import sys +import os +import subprocess +import vyos.defaults +import vyos.remote +from vyos.config import Config +from vyos.configtree import ConfigTree + + +if (len(sys.argv) < 2): + print("Need config file name to merge.") + print("Usage: merge [config path]") + sys.exit(0) + +file_name = sys.argv[1] + +configdir = vyos.defaults.directories['config'] + +protocols = ['scp', 'sftp', 'http', 'https', 'ftp', 'tftp'] + +if any(x in file_name for x in protocols): + config_file = vyos.remote.get_remote_config(file_name) + if not config_file: + sys.exit("No config file by that name.") +else: + canonical_path = "{0}/{1}".format(configdir, file_name) + first_err = None + try: + with open(canonical_path, 'r') as f: + config_file = f.read() + except Exception as err: + first_err = err + try: + with open(file_name, 'r') as f: + config_file = f.read() + except Exception as err: + print(first_err) + print(err) + sys.exit(1) + +path = None +if (len(sys.argv) > 2): + path = " ".join(sys.argv[2:]) + +merge_config_tree = ConfigTree(config_file) + +effective_config = Config() + +output_effective_config = effective_config.show_config() +effective_config_tree = ConfigTree(output_effective_config) + +effective_cmds = effective_config_tree.to_commands() +merge_cmds = merge_config_tree.to_commands() + +effective_cmd_list = effective_cmds.splitlines() +merge_cmd_list = merge_cmds.splitlines() + +effective_cmd_set = set(effective_cmd_list) +add_cmds = [ cmd for cmd in merge_cmd_list if cmd not in effective_cmd_set ] + +if path: + if not effective_config.exists(path): + print("path {} does not exist in running config; will use " + "root.".format(path)) + else: + add_cmds = [ cmd for cmd in add_cmds if path in cmd ] + +for cmd in add_cmds: + cmd = "/opt/vyatta/sbin/my_" + cmd + + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + print("Called process error: {}.".format(err)) + +if effective_config.session_changed(): + print("Merge complete. Use 'commit' to make changes effective.") +else: + print("No configuration changes to commit.") -- cgit v1.2.3 From 2d13d5741953a82ba1b232dd3e1c9efb98ec43a6 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 3 Jun 2019 10:37:35 -0500 Subject: T1423: Create known_hosts file if not present In the recent rewrite of the config merge script, support for merging remote config files checks and adds the host key in known_hosts; however, this function fails if known_hosts is not present. Fix. --- python/vyos/remote.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'python') diff --git a/python/vyos/remote.py b/python/vyos/remote.py index 372780c91..49936ec08 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -26,6 +26,9 @@ def check_and_add_host_key(host_name): needed. """ known_hosts = '{}/.ssh/known_hosts'.format(os.getenv('HOME')) + if not os.path.exists(known_hosts): + mode = 0o600 + os.mknod(known_hosts, 0o600) keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name) -- cgit v1.2.3 From 6763170830010c8cea2f17daee5f46b9203dab56 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 30 May 2019 13:46:08 -0500 Subject: T1334: Migration script runner rewrite Python script and support code to replace the vyatta_config_migrate.pl script. --- python/vyos/defaults.py | 6 +- python/vyos/formatversions.py | 109 +++++++++++++++++++++ python/vyos/migrator.py | 190 ++++++++++++++++++++++++++++++++++++ python/vyos/systemversions.py | 39 ++++++++ src/helpers/run-config-migration.py | 83 ++++++++++++++++ src/helpers/system-versions-foot.py | 39 ++++++++ 6 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 python/vyos/formatversions.py create mode 100644 python/vyos/migrator.py create mode 100644 python/vyos/systemversions.py create mode 100755 src/helpers/run-config-migration.py create mode 100755 src/helpers/system-versions-foot.py (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 0603efc42..da363b8e1 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -16,7 +16,11 @@ directories = { "data": "/usr/share/vyos/", - "config": "/opt/vyatta/etc/config" + "config": "/opt/vyatta/etc/config", + "current": "/opt/vyatta/etc/config-migrate/current", + "migrate": "/opt/vyatta/etc/config-migrate/migrate", } cfg_group = 'vyattacfg' + +cfg_vintage = 'vyatta' diff --git a/python/vyos/formatversions.py b/python/vyos/formatversions.py new file mode 100644 index 000000000..29117a5d3 --- /dev/null +++ b/python/vyos/formatversions.py @@ -0,0 +1,109 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import sys +import os +import re +import fileinput + +def read_vyatta_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', config_line): + if not re.match(r'/\* === vyatta-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s+=== \*/$', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + + return config_file_versions + +def read_vyos_versions(config_file): + config_file_versions = {} + + with open(config_file, 'r') as config_file_handle: + for config_line in config_file_handle: + if re.match(r'// vyos-config-version:.+', config_line): + if not re.match(r'// vyos-config-version:\s+"([\w,-]+@\d+:)+([\w,-]+@\d+)"\s*', config_line): + raise ValueError("malformed configuration string: " + "{}".format(config_line)) + + for pair in re.findall(r'([\w,-]+)@(\d+)', config_line): + config_file_versions[pair[0]] = int(pair[1]) + + return config_file_versions + +def remove_versions(config_file): + """ + Remove old version string. + """ + for line in fileinput.input(config_file, inplace=True): + if re.match(r'/\* Warning:.+ \*/$', line): + continue + if re.match(r'/\* === vyatta-config-version:.+=== \*/$', line): + continue + if re.match(r'/\* Release version:.+ \*/$', line): + continue + if re.match('// vyos-config-version:.+', line): + continue + if re.match('// Warning:.+', line): + continue + if re.match('// Release version:.+', line): + continue + sys.stdout.write(line) + +def format_versions_string(config_versions): + cfg_keys = list(config_versions.keys()) + cfg_keys.sort() + + component_version_strings = [] + + for key in cfg_keys: + cfg_vers = config_versions[key] + component_version_strings.append('{}@{}'.format(key, cfg_vers)) + + separator = ":" + component_version_string = separator.join(component_version_strings) + + return component_version_string + +def write_vyatta_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('/* Warning: Do not remove the following line. */\n') + config_file_handle.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + config_file_handle.write('/* Release version: {} */\n'.format(os_version_string)) + else: + sys.stdout.write('/* Warning: Do not remove the following line. */\n') + sys.stdout.write('/* === vyatta-config-version: "{}" === */\n'.format(component_version_string)) + sys.stdout.write('/* Release version: {} */\n'.format(os_version_string)) + +def write_vyos_versions_foot(config_file, component_version_string, + os_version_string): + if config_file: + with open(config_file, 'a') as config_file_handle: + config_file_handle.write('// Warning: Do not remove the following line.\n') + config_file_handle.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + config_file_handle.write('// Release version: {}\n'.format(os_version_string)) + else: + sys.stdout.write('// Warning: Do not remove the following line.\n') + sys.stdout.write('// vyos-config-version: "{}"\n'.format(component_version_string)) + sys.stdout.write('// Release version: {}\n'.format(os_version_string)) + diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py new file mode 100644 index 000000000..2d4bc7ffc --- /dev/null +++ b/python/vyos/migrator.py @@ -0,0 +1,190 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import sys +import os +import subprocess +import vyos.version +import vyos.defaults +import vyos.systemversions as systemversions +import vyos.formatversions as formatversions + +class MigratorError(Exception): + pass + +class Migrator(object): + def __init__(self, config_file, force=False, set_vintage=None): + self._config_file = config_file + self._force = force + self._set_vintage = set_vintage + self._config_file_vintage = None + self._changed = False + + def read_config_file_versions(self): + """ + Get component versions from config file footer and set vintage; + return empty dictionary if config string is missing. + """ + cfg_file = self._config_file + component_versions = {} + + cfg_versions = formatversions.read_vyatta_versions(cfg_file) + + if cfg_versions: + self._config_file_vintage = 'vyatta' + component_versions = cfg_versions + + cfg_versions = formatversions.read_vyos_versions(cfg_file) + + if cfg_versions: + self._config_file_vintage = 'vyos' + component_versions = cfg_versions + + return component_versions + + def update_vintage(self): + old_vintage = self._config_file_vintage + + if self._set_vintage: + self._config_file_vintage = self._set_vintage + + if not self._config_file_vintage: + self._config_file_vintage = vyos.defaults.cfg_vintage + + if self._config_file_vintage not in ['vyatta', 'vyos']: + raise MigratorError("Unknown vintage.") + + if self._config_file_vintage == old_vintage: + return False + else: + return True + + def run_migration_scripts(self, config_file_versions, system_versions): + """ + Run migration scripts iteratively, until config file version equals + system component version. + """ + cfg_versions = config_file_versions + sys_versions = system_versions + + sys_keys = list(sys_versions.keys()) + sys_keys.sort() + + rev_versions = {} + + for key in sys_keys: + sys_ver = sys_versions[key] + if key in cfg_versions: + cfg_ver = cfg_versions[key] + else: + cfg_ver = 0 + + migrate_script_dir = os.path.join( + vyos.defaults.directories['migrate'], key) + + while cfg_ver < sys_ver: + next_ver = cfg_ver + 1 + + migrate_script = os.path.join(migrate_script_dir, + '{}-to-{}'.format(cfg_ver, next_ver)) + + try: + subprocess.check_output([migrate_script, + self._config_file]) + except FileNotFoundError: + pass + except subprocess.CalledProcessError as err: + print("Called process error: {}.".format(err)) + sys.exit(1) + + cfg_ver = next_ver + + rev_versions[key] = cfg_ver + + return rev_versions + + def write_config_file_versions(self, cfg_versions): + """ + Write new versions string. + """ + versions_string = formatversions.format_versions_string(cfg_versions) + + os_version_string = vyos.version.get_version() + + if self._config_file_vintage == 'vyatta': + formatversions.write_vyatta_versions_foot(self._config_file, + versions_string, + os_version_string) + + if self._config_file_vintage == 'vyos': + formatversions.write_vyos_versions_foot(self._config_file, + versions_string, + os_version_string) + + def run(self): + """ + Gather component versions from config file and system. + Run migration scripts. + Update vintage ('vyatta' or 'vyos'), if needed. + If changed, remove old versions string from config file, and + write new versions string. + """ + cfg_file = self._config_file + + cfg_versions = self.read_config_file_versions() + if self._force: + # This will force calling all migration scripts: + cfg_versions = {} + + sys_versions = systemversions.get_system_versions() + + rev_versions = self.run_migration_scripts(cfg_versions, sys_versions) + + if rev_versions != cfg_versions: + self._changed = True + + if self.update_vintage(): + self._changed = True + + if not self._changed: + return + + formatversions.remove_versions(cfg_file) + + self.write_config_file_versions(rev_versions) + + +class VirtualMigrator(Migrator): + def __init__(self, config_file, vintage='vyos'): + super().__init__(config_file, set_vintage = vintage) + + def run(self): + cfg_file = self._config_file + + cfg_versions = self.read_config_file_versions() + if not cfg_versions: + raise MigratorError("Config file has no version information;" + " virtual migration not possible.") + + if self.update_vintage(): + self._changed = True + + if not self._changed: + return + + formatversions.remove_versions(cfg_file) + + self.write_config_file_versions(cfg_versions) + diff --git a/python/vyos/systemversions.py b/python/vyos/systemversions.py new file mode 100644 index 000000000..9b3f4f413 --- /dev/null +++ b/python/vyos/systemversions.py @@ -0,0 +1,39 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import os +import re +import sys +import vyos.defaults + +def get_system_versions(): + """ + Get component versions from running system; critical failure if + unable to read migration directory. + """ + system_versions = {} + + try: + version_info = os.listdir(vyos.defaults.directories['current']) + except OSError as err: + print("OS error: {}".format(err)) + sys.exit(1) + + for info in version_info: + if re.match(r'[\w,-]+@\d+', info): + pair = info.split('@') + system_versions[pair[0]] = int(pair[1]) + + return system_versions diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py new file mode 100755 index 000000000..a57a19cdf --- /dev/null +++ b/src/helpers/run-config-migration.py @@ -0,0 +1,83 @@ +#!/usr/bin/python3 + +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import os +import sys +import argparse +import datetime +import subprocess +from vyos.migrator import Migrator, VirtualMigrator + +def main(): + argparser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter) + argparser.add_argument('config_file', type=str, + help="configuration file to migrate") + argparser.add_argument('--force', action='store_true', + help="Force calling of all migration scripts.") + argparser.add_argument('--set-vintage', type=str, + choices=['vyatta', 'vyos'], + help="Set the format for the config version footer in config" + " file:\n" + "set to 'vyatta':\n" + "(for '/* === vyatta-config-version ... */' format)\n" + "or 'vyos':\n" + "(for '// vyos-config-version ...' format).") + argparser.add_argument('--virtual', action='store_true', + help="Update the format of the trailing comments in" + " config file,\nfrom 'vyatta' to 'vyos'; no migration" + " scripts are run.") + args = argparser.parse_args() + + config_file_name = args.config_file + force_on = args.force + vintage = args.set_vintage + virtual = args.virtual + + if not os.access(config_file_name, os.R_OK): + print("Read error: {}.".format(config_file_name)) + sys.exit(1) + + if not os.access(config_file_name, os.W_OK): + print("Write error: {}.".format(config_file_name)) + sys.exit(1) + + separator = "." + backup_file_name = separator.join([config_file_name, + '{0:%Y-%m-%d-%H%M%S}'.format(datetime.datetime.now()), + 'pre-migration']) + + try: + subprocess.check_call(['cp', '-p', config_file_name, + backup_file_name]) + except subprocess.CalledProcessError as err: + print("Called process error: {}.".format(err)) + sys.exit(1) + + if not virtual: + migration = Migrator(config_file_name, force=force_on, + set_vintage=vintage) + else: + migration = VirtualMigrator(config_file_name) + + migration.run() + + if not migration._changed: + os.remove(backup_file_name) + +if __name__ == '__main__': + main() diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py new file mode 100755 index 000000000..c33e41d79 --- /dev/null +++ b/src/helpers/system-versions-foot.py @@ -0,0 +1,39 @@ +#!/usr/bin/python3 + +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import sys +import vyos.formatversions as formatversions +import vyos.systemversions as systemversions +import vyos.defaults +import vyos.version + +sys_versions = systemversions.get_system_versions() + +component_string = formatversions.format_versions_string(sys_versions) + +os_version_string = vyos.version.get_version() + +sys.stdout.write("\n\n") +if vyos.defaults.cfg_vintage == 'vyos': + formatversions.write_vyos_versions_foot(None, component_string, + os_version_string) +elif vyos.defaults.cfg_vintage == 'vyatta': + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) +else: + formatversions.write_vyatta_versions_foot(None, component_string, + os_version_string) -- cgit v1.2.3 From 5e7cf2bb32ca5860d59d3a3c1fce9e3bba2236a2 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 12 Jun 2019 08:03:29 +0200 Subject: T1432: initial implementation of the config write API. --- python/vyos/configsession.py | 94 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 python/vyos/configsession.py (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py new file mode 100644 index 000000000..4e9d30fa7 --- /dev/null +++ b/python/vyos/configsession.py @@ -0,0 +1,94 @@ +# configsession -- the write API for the VyOS running config +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import re +import subprocess + +CLI_SHELL_API = '/bin/cli-shell-api' +SET = '/opt/vyatta/sbin/my_set' +DELETE = '/opt/vyatta/sbin/my_delete' +COMMENT = '/opt/vyatta/sbin/my_comment' +COMMIT = '/opt/vyatta/sbin/my_commit' + +APP = "vyos-api" + + +class ConfigSessionError(Exception): + pass + + +class ConfigSession(object): + """ + The write API of VyOS. + """ + def __init__(self, session_id, app=APP): + """ + Creates a new config session. + + Args: + session_id (str): Session identifier + app (str): Application name, purely informational + + Note: + The session identifier MUST be globally unique within the system. + The best practice is to only have one ConfigSession object per process + and used the PID for the session identifier. + """ + + env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)]) + + # Extract actual variables from the chunk of shell it outputs + # XXX: it's better to extend cli-shell-api to provide easily readable output + env_list = re.findall(r'([A-Z_]+)=([^;\s]+)', env_str.decode()) + + session_env = os.environ + for k, v in env_list: + session_env[k] = v + + self.__session_env = session_env + self.__session_env["COMMIT_VIA"] = app + + self.__run_command([CLI_SHELL_API, 'setupSession']) + + def __run_command(self, cmd_list): + p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env) + result = p.wait() + output = p.stdout.read().decode() + if result != 0: + raise VyOSAPIError(output) + + def set(self, path, value=None): + if not value: + value = [] + else: + value = [value] + self.__run_command([SET] + path + value) + + def delete(self, path, value=None): + if not value: + value = [] + else: + value = [value] + self.__run_command([DELETE] + path + value) + + def comment(self, path, value=None): + if not value: + value = [""] + else: + value = [value] + self.__run_command([COMMENT] + path + value) + + def commit(self): + self.__run_command([COMMIT]) -- cgit v1.2.3 From 6f42122bc4b8ae8a287f0350eba4d8cd2f5f9649 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 12 Jun 2019 10:43:08 +0200 Subject: T1432: correct the ConfigSessionError exception name. --- python/vyos/configsession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 4e9d30fa7..b989d3be5 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -67,7 +67,7 @@ class ConfigSession(object): result = p.wait() output = p.stdout.read().decode() if result != 0: - raise VyOSAPIError(output) + raise ConfigSessionError(output) def set(self, path, value=None): if not value: -- cgit v1.2.3 From 441f95d499f42f57b3a15d78aec826f794fab59f Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 12 Jun 2019 12:04:34 -0500 Subject: T1397: use revised migration method --- python/vyos/migrator.py | 2 ++ src/helpers/vyos-merge-config.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) (limited to 'python') diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 2d4bc7ffc..59d68f0f7 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -165,6 +165,8 @@ class Migrator(object): self.write_config_file_versions(rev_versions) + def config_changed(self): + return self._changed class VirtualMigrator(Migrator): def __init__(self, config_file, vintage='vyos'): diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py index bb2919de2..e97a1c08d 100755 --- a/src/helpers/vyos-merge-config.py +++ b/src/helpers/vyos-merge-config.py @@ -18,8 +18,10 @@ import sys import os import subprocess +import tempfile import vyos.defaults import vyos.remote +import vyos.migrator from vyos.config import Config from vyos.configtree import ConfigTree @@ -59,6 +61,16 @@ path = None if (len(sys.argv) > 2): path = " ".join(sys.argv[2:]) +with tempfile.NamedTemporaryFile() as file_to_migrate: + with open(file_to_migrate.name, 'w') as fd: + fd.write(config_file) + + migration = vyos.migrator.Migrator(file_to_migrate.name) + migration.run() + if migration.config_changed(): + with open(file_to_migrate.name, 'r') as fd: + config_file = fd.read() + merge_config_tree = ConfigTree(config_file) effective_config = Config() -- cgit v1.2.3 From 29df430906c830146e6cc9b7edda9be836a01837 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 13 Jun 2019 03:10:08 +0200 Subject: T1431: make it possible to obtain session environment and run vyos.config functions under it. This is required for programs running outside a CLI session, like the future API daemon. --- python/vyos/config.py | 11 +++++++++-- python/vyos/configsession.py | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index 9a5125eb9..96e9631e0 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -87,9 +87,13 @@ class Config(object): the only state it keeps is relative *config path* for convenient access to config subtrees. """ - def __init__(self): + def __init__(self, session_env=None): self._cli_shell_api = "/bin/cli-shell-api" self._level = "" + if session_env: + self.__session_env = session_env + else: + self.__session_env = None def _make_command(self, op, path): args = path.split() @@ -97,7 +101,10 @@ class Config(object): return cmd def _run(self, cmd): - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + if self.__session_env: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) + else: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) out = p.stdout.read() p.wait() if p.returncode != 0: diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index b989d3be5..c84d80a77 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -69,6 +69,9 @@ class ConfigSession(object): if result != 0: raise ConfigSessionError(output) + def get_session_env(self): + return self.__session_env + def set(self, path, value=None): if not value: value = [] -- cgit v1.2.3 From 6b5f0dd5e59b4e0b0170a087a50bfd61bc5f14ac Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 16 Jun 2019 10:51:07 +0200 Subject: T1432: add a discard function to vyos.configsession --- python/vyos/configsession.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index c84d80a77..a27470eeb 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -21,7 +21,9 @@ SET = '/opt/vyatta/sbin/my_set' DELETE = '/opt/vyatta/sbin/my_delete' COMMENT = '/opt/vyatta/sbin/my_comment' COMMIT = '/opt/vyatta/sbin/my_commit' +DISCARD = '/opt/vyatta/sbin/my_discard' +# Default "commit via" string APP = "vyos-api" @@ -95,3 +97,6 @@ class ConfigSession(object): def commit(self): self.__run_command([COMMIT]) + + def discard(self): + self.__run_command([DISCARD]) -- cgit v1.2.3 From 303e8cb27560ade4bf0c9e6b9bc453c2f00fe799 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 16 Jun 2019 11:19:34 +0200 Subject: T1432: add a finalizer to vyos.configsession to avoid leaking sessions. --- python/vyos/configsession.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index a27470eeb..39a9713e0 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -14,6 +14,7 @@ import os import re +import sys import subprocess CLI_SHELL_API = '/bin/cli-shell-api' @@ -50,6 +51,7 @@ class ConfigSession(object): """ env_str = subprocess.check_output([CLI_SHELL_API, 'getSessionEnv', str(session_id)]) + self.__session_id = session_id # Extract actual variables from the chunk of shell it outputs # XXX: it's better to extend cli-shell-api to provide easily readable output @@ -64,6 +66,14 @@ class ConfigSession(object): self.__run_command([CLI_SHELL_API, 'setupSession']) + def __del__(self): + try: + output = subprocess.check_output([CLI_SHELL_API, 'teardownSession'], env=self.__session_env).decode().strip() + if output: + print("cli-shell-api teardownSession output for sesion {0}: {1}".format(self.__session_id, output), file=sys.stderr) + except Exception as e: + print("Could not tear down session {0}: {1}".format(self.__session_id, e), file=sys.stderr) + def __run_command(self, cmd_list): p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=self.__session_env) result = p.wait() -- cgit v1.2.3 From 9bf7d03ff7342e7f87710df6bcc15beceed9582c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 16 Jun 2019 21:02:25 +0200 Subject: T1432: inject VyOS-specific environment variables into the session environment. They are widely referenced by command templates, but a process started as a service doesn't automatically get them. --- python/vyos/configsession.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 39a9713e0..78f332d66 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -25,7 +25,42 @@ COMMIT = '/opt/vyatta/sbin/my_commit' DISCARD = '/opt/vyatta/sbin/my_discard' # Default "commit via" string -APP = "vyos-api" +APP = "vyos-http-api" + +# When started as a service rather than from a user shell, +# the process lacks the VyOS-specific environment that comes +# from bash configs, so we have to inject it +# XXX: maybe it's better to do via a systemd environment file +def inject_vyos_env(env): + env['VYATTA_CFG_GROUP_NAME'] = 'vyattacfg' + env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin' + env['vyatta_bindir']= '/opt/vyatta/bin' + env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' + env['vyatta_configdir'] = '/opt/vyatta/config' + env['vyatta_datadir'] = '/opt/vyatta/share' + env['vyatta_datarootdir'] = '/opt/vyatta/share' + env['vyatta_libdir'] = '/opt/vyatta/lib' + env['vyatta_libexecdir'] = '/opt/vyatta/libexec' + env['vyatta_op_templates'] = '/opt/vyatta/share/vyatta-op/templates' + env['vyatta_prefix'] = '/opt/vyatta' + env['vyatta_sbindir'] = '/opt/vyatta/sbin' + env['vyatta_sysconfdir'] = '/opt/vyatta/etc' + env['vyos_bin_dir'] = '/usr/bin' + env['vyos_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' + env['vyos_completion_dir'] = '/usr/libexec/vyos/completion' + env['vyos_configdir'] = '/opt/vyatta/config' + env['vyos_conf_scripts_dir'] = '/usr/libexec/vyos/conf_mode' + env['vyos_datadir'] = '/opt/vyatta/share' + env['vyos_datarootdir']= '/opt/vyatta/share' + env['vyos_libdir'] = '/opt/vyatta/lib' + env['vyos_libexec_dir'] = '/usr/libexec/vyos' + env['vyos_op_scripts_dir'] = '/usr/libexec/vyos/op_mode' + env['vyos_op_templates'] = '/opt/vyatta/share/vyatta-op/templates' + env['vyos_prefix'] = '/opt/vyatta' + env['vyos_sbin_dir'] = '/usr/sbin' + env['vyos_validators_dir'] = '/usr/libexec/vyos/validators' + + return env class ConfigSessionError(Exception): @@ -58,6 +93,7 @@ class ConfigSession(object): env_list = re.findall(r'([A-Z_]+)=([^;\s]+)', env_str.decode()) session_env = os.environ + session_env = inject_vyos_env(session_env) for k, v in env_list: session_env[k] = v -- cgit v1.2.3 From d56d52990f5b30a1b03b2479767e91aa3aa2cdc5 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 1 Jul 2019 13:57:52 -0500 Subject: [service https] T1443: add service https and service https api --- debian/rules | 3 + interface-definitions/https.xml | 87 ++++++++++++++++++++++++++ python/vyos/defaults.py | 1 + src/conf_mode/http-api.py | 104 +++++++++++++++++++++++++++++++ src/conf_mode/https.py | 132 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 327 insertions(+) create mode 100644 interface-definitions/https.xml create mode 100755 src/conf_mode/http-api.py create mode 100755 src/conf_mode/https.py (limited to 'python') diff --git a/debian/rules b/debian/rules index b06117922..952867a76 100755 --- a/debian/rules +++ b/debian/rules @@ -77,3 +77,6 @@ override_dh_auto_install: # Install systemd service units mkdir -p $(DIR)/lib/systemd/system cp -r src/systemd/* $(DIR)/lib/systemd/system + + # Make directory for generated configuration file + mkdir -p $(DIR)/etc/vyos diff --git a/interface-definitions/https.xml b/interface-definitions/https.xml new file mode 100644 index 000000000..828de449c --- /dev/null +++ b/interface-definitions/https.xml @@ -0,0 +1,87 @@ + + + + + + + + HTTPS configuration + 1001 + + + + + Addresses to listen for HTTPS requests + + ipv4 + HTTPS IPv4 address + + + ipv6 + HTTPS IPv6 address + + + + + + + + + + + VyOS HTTP API configuration + 1002 + + + + + Port for HTTP API service + + 1-65535 + Numeric IP port + + + + + + + + + HTTP API keys + + + + + HTTP API id + + + + + HTTP API plaintext key + + + + + + + + + Enforce strict path checking + + + + + + Debug + + + + + + + + + + + + diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index da363b8e1..f23e15631 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -16,6 +16,7 @@ directories = { "data": "/usr/share/vyos/", + "conf_mode": "/usr/libexec/vyos/conf_mode", "config": "/opt/vyatta/etc/config", "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py new file mode 100755 index 000000000..7d618dded --- /dev/null +++ b/src/conf_mode/http-api.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import sys +import os +import subprocess +import json + +import vyos.defaults +from vyos.config import Config +from vyos import ConfigError + +config_file = '/etc/vyos/http-api.conf' + +default_config_data = { + 'listen_address' : '127.0.0.1', + 'port' : '8080', + 'strict' : 'false', + 'debug' : 'false', + 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] +} + +vyos_conf_scripts_dir=vyos.defaults.directories['conf_mode'] + +# XXX: this model will need to be extended for tag nodes +dependencies = [ + 'https.py', +] + +def get_config(): + http_api = default_config_data + conf = Config() + if not conf.exists('service https api'): + return None + else: + conf.set_level('service https api') + + if conf.exists('strict'): + http_api['strict'] = 'true' + + if conf.exists('debug'): + http_api['debug'] = 'true' + + if conf.exists('port'): + port = conf.return_value('port') + http_api['port'] = port + + if conf.exists('keys'): + for name in conf.list_nodes('keys id'): + if conf.exists('keys id {0} key'.format(name)): + key = conf.return_value('keys id {0} key'.format(name)) + new_key = { 'id': name, 'key': key } + http_api['api_keys'].append(new_key) + + return http_api + +def verify(http_api): + return None + +def generate(http_api): + if http_api is None: + return None + + with open(config_file, 'w') as f: + json.dump(http_api, f, indent=2) + + return None + +def apply(http_api): + if http_api is not None: + os.system('sudo systemctl restart vyos-http-api.service') + for dep in dependencies: + cmd = '{0}/{1}'.format(vyos_conf_scripts_dir, dep) + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + raise ConfigError("{}.".format(err)) + else: + os.system('sudo systemctl stop vyos-http-api.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py new file mode 100755 index 000000000..dae51dd7d --- /dev/null +++ b/src/conf_mode/https.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import sys +import os + +import jinja2 + +from vyos.config import Config +from vyos import ConfigError + +config_file = '/etc/nginx/sites-available/default' + +# Please be careful if you edit the template. +config_tmpl = """ + +### Autogenerated by http-api.py ### +# Default server configuration +# +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + return 302 https://$server_name$request_uri; +} + +server { + + # SSL configuration + # + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + # + # Self signed certs generated by the ssl-cert package + # Don't use them in a production server! + # + include snippets/snakeoil.conf; + +{% for l_addr in listen_address %} + server_name {{ l_addr }}; +{% endfor %} + + location / { +{% if api %} + proxy_pass http://localhost:{{ api.port }}; + proxy_buffering off; +{% endif %} + } + + error_page 501 502 503 =200 @50*_json; + + location @50*_json { + default_type application/json; + return 200 '{"error": "Start service in configuration mode: set service https api"}'; + } + +} +""" + +default_config_data = { + 'listen_address' : [ '127.0.0.1' ] +} + +default_api_config_data = { + 'port' : '8080', +} + +def get_config(): + https = default_config_data + conf = Config() + if not conf.exists('service https'): + return None + else: + conf.set_level('service https') + + if conf.exists('listen-address'): + addrs = conf.return_values('listen-address') + https['listen_address'] = addrs[:] + + if conf.exists('api'): + https['api'] = default_api_config_data + + if conf.exists('api port'): + port = conf.return_value('api port') + https['api']['port'] = port + + return https + +def verify(https): + return None + +def generate(https): + if https is None: + return None + + tmpl = jinja2.Template(config_tmpl, trim_blocks=True) + config_text = tmpl.render(https) + with open(config_file, 'w') as f: + f.write(config_text) + + return None + +def apply(https): + if https is not None: + os.system('sudo systemctl restart nginx.service') + else: + os.system('sudo systemctl stop nginx.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From c1fdee12f94dcf4395992152358d03cb8c74f155 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 03:35:31 +0200 Subject: T1503: add functions for commit lock checking and waiting. --- python/vyos/defaults.py | 2 ++ python/vyos/util.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index f23e15631..524b80424 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -25,3 +25,5 @@ directories = { cfg_group = 'vyattacfg' cfg_vintage = 'vyatta' + +commit_lock = '/opt/vyatta/config/.lock' diff --git a/python/vyos/util.py b/python/vyos/util.py index 8b5342575..6ab606983 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1,4 +1,4 @@ -# Copyright 2018 VyOS maintainers and contributors +# Copyright 2019 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -16,6 +16,9 @@ import os import re import grp +import time +import subprocess + import psutil import vyos.defaults @@ -131,3 +134,45 @@ def file_is_persistent(path): return (False, warning) else: return (True, None) + +def commit_in_progress(): + """ Not to be used in normal op mode scripts! """ + + # The CStore backend locks the config by opening a file + # The file is not removed after commit, so just checking + # if it exists is insufficient, we need to know if it's open by anyone + + # There are two ways to check if any other process keeps a file open. + # The first one is to try opening it and see if the OS objects. + # That's faster but prone to race conditions and can be intrusive. + # The other one is to actually check if any process keeps it open. + # It's non-intrusive but needs root permissions, else you can't check + # processes of other users. + # + # Since this will be used in scripts that modify the config outside of the CLI + # framework, those knowingly have root permissions. + # For everything else, we add a safeguard. + id = subprocess.check_output(['/usr/bin/id', '-u']).decode().strip() + if id != '0': + raise OSError("This functions needs root permissions to return correct results") + + for proc in psutil.process_iter(): + try: + files = proc.open_files() + if files: + for f in files: + if f.path == vyos.defaults.commit_lock: + return True + except psutil.NoSuchProcess as err: + # Process died before we could examine it + pass + # Default case + return False + +def wait_for_commit_lock(): + """ Not to be used in normal op mode scripts! """ + + # Very synchronous approach to multiprocessing + while commit_in_progress(): + time.sleep(1) + -- cgit v1.2.3 From 3f7322d7f15ac348f1d06ba411c935956e276a76 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 12:40:27 +0200 Subject: [vyos.config] T1505: correct return_effective_values output splitting. --- python/vyos/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index 96e9631e0..c9c73b971 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -405,7 +405,8 @@ class Config(object): else: try: out = self._run(self._make_command('returnEffectiveValues', full_path)) - return out + values = re.findall(r"\'(.*?)\'", out) + return values except VyOSError: return(default) -- cgit v1.2.3