summaryrefslogtreecommitdiff
path: root/python/vyos/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/config.py')
-rw-r--r--python/vyos/config.py239
1 files changed, 125 insertions, 114 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py
index c9c73b971..3a340b2da 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -1,4 +1,4 @@
-# Copyright 2017 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2017, 2019 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,7 +17,7 @@
A library for reading VyOS running config data.
This library is used internally by all config scripts of VyOS,
-but its API should be considered stable and it is safe to use
+but its API should be considered stable and safe to use
in user scripts.
Note that this module will not work outside VyOS.
@@ -43,8 +43,8 @@ For example, under "system", the names of all valid child nodes are predefined
To the contrary, children of the "system task-scheduler task" node can have arbitrary names.
Such nodes are called *tag nodes*. This terminology is confusing but we keep using it for lack
-of a better word. The knowledge of whether in "task Foo" the "tag" is "task" or "Foo" is lost
-in time, luckily, the distinction is irrelevant in practice.
+of a better word. No one remembers if the "tag" in "task Foo" is "task" or "Foo",
+but the distinction is irrelevant in practice.
Configuration modes
###################
@@ -53,21 +53,21 @@ VyOS has two distinct modes: operational mode and configuration mode. When a use
the CLI is in the operational mode. In this mode, only the running (effective) config is accessible for reading.
When a user enters the "configure" command, a configuration session is setup. Every config session
-has its *proposed* config built on top of the current running config. When changes are commited, if commit succeeds,
+has its *proposed* (or *session*) config built on top of the current running config. When changes are commited, if commit succeeds,
the proposed config is merged into the running config.
-For this reason, this library has two sets of functions. The base versions, such as ``exists`` or ``return_value``
-are only usable in configuration mode. They take all nodes into account, in both proposed and running configs.
-Configuration scripts require access to uncommited changes for obvious reasons. Configuration mode completion helpers
-should also use these functions because not having nodes you've just created in completion is annoying.
+In configuration mode, "base" functions like `exists`, `return_value` return values from the session config,
+while functions prefixed "effective" return values from the running config.
+
+In operational mode, all functions return values from the running config.
-However, in operational mode, only the running config is available. Currently, you need to use special functions
-for reading it from operational mode scripts, they can be distinguished by the word "effective" in their names.
-In the future base versions may be made to detect if they are called from a config session or not.
"""
-import subprocess
+import os
import re
+import subprocess
+
+import vyos.configtree
class VyOSError(Exception):
@@ -89,17 +89,51 @@ class Config(object):
"""
def __init__(self, session_env=None):
self._cli_shell_api = "/bin/cli-shell-api"
- self._level = ""
+ self._level = []
if session_env:
self.__session_env = session_env
else:
self.__session_env = None
+ # Running config can be obtained either from op or conf mode, it always succeeds
+ # (if config system is initialized at all).
+ if os.path.isfile('/tmp/vyos-config-status'):
+ running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', 'showConfig'])
+ else:
+ with open('/opt/vyatta/etc/config/config.boot') as f:
+ running_config_text = f.read()
+
+ # Session config ("active") only exists in conf mode.
+ # Trying to obtain it from op mode will cause a fatal cli-shell-api error.
+ # If that happens, we assume that a script is running from op mode and use the running config
+ # for the "session config" variable as well.
+ try:
+ session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', 'showConfig'])
+ except VyOSError:
+ session_config_text = running_config_text
+
+ self._session_config = vyos.configtree.ConfigTree(session_config_text)
+ self._running_config = vyos.configtree.ConfigTree(running_config_text)
+
def _make_command(self, op, path):
args = path.split()
cmd = [self._cli_shell_api, op] + args
return cmd
+ def _make_path(self, path):
+ # Backwards-compatibility stuff: original implementation used string paths
+ # libvyosconfig paths are lists, but since node names cannot contain whitespace,
+ # splitting at whitespace is reasonably safe.
+ # It may cause problems with exists() when it's used for checking values,
+ # since values may contain whitespace.
+ if isinstance(path, str):
+ path = re.split(r'\s*', path)
+ elif isinstance(path, list):
+ pass
+ else:
+ raise TypeError("Path must be a whitespace-separated string or a list")
+ return (self._level + path)
+
def _run(self, cmd):
if self.__session_env:
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env)
@@ -126,7 +160,12 @@ class Config(object):
# Make sure there's always a space between default path (level)
# and path supplied as method argument
# XXX: for small strings in-place concatenation is not a problem
- self._level = path + " "
+ if isinstance(path, str):
+ self._level = re.split(r'\s*', path)
+ elif isinstance(path, list):
+ self._level = path
+ else:
+ raise TypeError("Level path must be either a whitespace-separated string or a list")
def get_level(self):
"""
@@ -135,7 +174,7 @@ class Config(object):
Returns:
str: current edit level
"""
- return(self._level.strip())
+ return(self._level)
def exists(self, path):
"""
@@ -148,11 +187,20 @@ class Config(object):
This function cannot be used outside a configuration sessions.
In operational mode scripts, use ``exists_effective``.
"""
- try:
- self._run(self._make_command('exists', self._level + path))
+ if self._session_config.exists(self._make_path(path)):
return True
- except VyOSError:
- return False
+ else:
+ # libvyosconfig exists() works only for _nodes_, not _values_
+ # libvyattacfg one also worked for values, so we emulate that case here
+ path = re.split(r'\s*', path)
+ path_without_value = path[:-1]
+ path_str = " ".join(path_without_value)
+ try:
+ value = self._session_config.return_value(self._make_path(path_str))
+ return (value == path[-1])
+ except vyos.configtree.ConfigTreeError:
+ # node doesn't exist at all
+ return False
def session_changed(self):
"""
@@ -203,7 +251,8 @@ class Config(object):
It also returns False if node doesn't exist.
"""
try:
- self._run(self._make_command('isMulti', self._level + path))
+ path = " ".join(self._level) + " " + path
+ self._run(self._make_command('isMulti', path))
return True
except VyOSError:
return False
@@ -220,7 +269,8 @@ class Config(object):
It also returns False if node doesn't exist.
"""
try:
- self._run(self._make_command('isTag', self._level + path))
+ path = " ".join(self._level) + " " + path
+ self._run(self._make_command('isTag', path))
return True
except VyOSError:
return False
@@ -237,7 +287,8 @@ class Config(object):
It also returns False if node doesn't exist.
"""
try:
- self._run(self._make_command('isLeaf', self._level + path))
+ path = " ".join(self._level) + " " + path
+ self._run(self._make_command('isLeaf', path))
return True
except VyOSError:
return False
@@ -254,9 +305,6 @@ class Config(object):
str: Node value, if it has any
None: if node is valueless *or* if it doesn't exist
- Raises:
- VyOSError: if node is not a single-value leaf node
-
Note:
Due to the issue with treatment of valueless nodes by this function,
valueless nodes should be checked with ``exists`` instead.
@@ -264,17 +312,15 @@ class Config(object):
This function cannot be used outside a configuration session.
In operational mode scripts, use ``return_effective_value``.
"""
- full_path = self._level + path
- if self.is_multi(path):
- raise VyOSError("Cannot use return_value on multi node: {0}".format(full_path))
- elif not self.is_leaf(path):
- raise VyOSError("Cannot use return_value on non-leaf node: {0}".format(full_path))
+ try:
+ value = self._session_config.return_value(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ value = None
+
+ if not value:
+ return(default)
else:
- try:
- out = self._run(self._make_command('returnValue', full_path))
- return out
- except VyOSError:
- return(default)
+ return(value)
def return_values(self, path, default=[]):
"""
@@ -285,27 +331,21 @@ class Config(object):
Returns:
str list: Node values, if it has any
- None: if node does not exist
-
- Raises:
- VyOSError: if node is not a multi-value leaf node
+ []: if node does not exist
Note:
This function cannot be used outside a configuration session.
In operational mode scripts, use ``return_effective_values``.
"""
- full_path = self._level + path
- if not self.is_multi(path):
- raise VyOSError("Cannot use return_values on non-multi node: {0}".format(full_path))
- elif not self.is_leaf(path):
- raise VyOSError("Cannot use return_values on non-leaf node: {0}".format(full_path))
+ try:
+ values = self._session_config.return_values(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ values = []
+
+ if not values:
+ return(default)
else:
- try:
- out = self._run(self._make_command('returnValues', full_path))
- values = re.findall(r"\'(.*?)\'", out)
- return values
- except VyOSError:
- return(default)
+ return(values)
def list_nodes(self, path, default=[]):
"""
@@ -317,26 +357,16 @@ class Config(object):
Returns:
string list: child node names
- Raises:
- VyOSError: if the node is not a tag node
-
- Note:
- There is no way to list all children of a non-tag node in
- the current config backend.
-
- This function cannot be used outside a configuration session.
- In operational mode scripts, use ``list_effective_nodes``.
"""
- full_path = self._level + path
- if self.is_tag(path):
- try:
- out = self._run(self._make_command('listNodes', full_path))
- values = re.findall(r"\'(.*?)\'", out)
- return values
- except VyOSError:
- return(default)
+ try:
+ nodes = self._session_config.list_nodes(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ nodes = []
+
+ if not nodes:
+ return(default)
else:
- raise VyOSError("Cannot use list_nodes on a non-tag node: {0}".format(full_path))
+ return(nodes)
def exists_effective(self, path):
"""
@@ -352,11 +382,7 @@ class Config(object):
This function is safe to use in operational mode. In configuration mode,
it ignores uncommited changes.
"""
- try:
- self._run(self._make_command('existsEffective', self._level + path))
- return True
- except VyOSError:
- return False
+ return(self._running_config.exists(self._make_path(path)))
def return_effective_value(self, path, default=None):
"""
@@ -368,21 +394,17 @@ class Config(object):
Returns:
str: Node value
-
- Raises:
- VyOSError: if node is not a multi-value leaf node
"""
- full_path = self._level + path
- if self.is_multi(path):
- raise VyOSError("Cannot use return_effective_value on multi node: {0}".format(full_path))
- elif not self.is_leaf(path):
- raise VyOSError("Cannot use return_effective_value on non-leaf node: {0}".format(full_path))
+ try:
+ value = self._running_config.return_value(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ value = None
+
+ if not value:
+ return(default)
else:
- try:
- out = self._run(self._make_command('returnEffectiveValue', full_path))
- return out
- except VyOSError:
- return(default)
+ return(value)
+
def return_effective_values(self, path, default=[]):
"""
@@ -393,22 +415,16 @@ class Config(object):
Returns:
str list: A list of values
-
- Raises:
- VyOSError: if node is not a multi-value leaf node
"""
- full_path = self._level + path
- if not self.is_multi(path):
- raise VyOSError("Cannot use return_effective_values on non-multi node: {0}".format(full_path))
- elif not self.is_leaf(path):
- raise VyOSError("Cannot use return_effective_values on non-leaf node: {0}".format(full_path))
+ try:
+ values = self._running_config.return_values(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ values = []
+
+ if not values:
+ return(default)
else:
- try:
- out = self._run(self._make_command('returnEffectiveValues', full_path))
- values = re.findall(r"\'(.*?)\'", out)
- return values
- except VyOSError:
- return(default)
+ return(values)
def list_effective_nodes(self, path, default=[]):
"""
@@ -422,18 +438,13 @@ class Config(object):
Raises:
VyOSError: if the node is not a tag node
-
- Note:
- There is no way to list all children of a non-tag node in
- the current config backend.
"""
- full_path = self._level + path
- if self.is_tag(path):
- try:
- out = self._run(self._make_command('listEffectiveNodes', full_path))
- values = out.split()
- return list(map(lambda x: re.sub(r'^\'(.*)\'$', r'\1',x), values))
- except VyOSError:
- return(default)
+ try:
+ nodes = self._running_config.list_nodes(self._make_path(path))
+ except vyos.configtree.ConfigTreeError:
+ nodes = []
+
+ if not nodes:
+ return(default)
else:
- raise VyOSError("Cannot use list_effective_nodes on a non-tag node: {0}".format(full_path))
+ return(nodes)