mirror of
				https://github.com/vyos/vyos-documentation.git
				synced 2025-11-04 00:02:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			145 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
.. _coding_guidelines:
 | 
						|
 | 
						|
Python Coding Guidelines
 | 
						|
========================
 | 
						|
 | 
						|
The switch to the Python programming language for new code is not merely a change
 | 
						|
of the language, but a chance to rethink and improve the programming approach.
 | 
						|
 | 
						|
Let's face it: VyOS is full of spaghetti code where logic for reading the VyOS
 | 
						|
config, generating daemon configs, and restarting processes is all mixed up.
 | 
						|
 | 
						|
Python (or any other language, for that matter) does not provide automatic protection
 | 
						|
from bad design, so we need to also devise design guidelines and follow them to
 | 
						|
keep the system extensible and maintainable.
 | 
						|
 | 
						|
Configuration script structure and behaviour
 | 
						|
--------------------------------------------
 | 
						|
 | 
						|
.. code-block:: python
 | 
						|
 | 
						|
  import sys
 | 
						|
 | 
						|
  from vyos.config import Config
 | 
						|
  from vyos.util import ConfigError
 | 
						|
 | 
						|
  def get_config():
 | 
						|
      vc = Config()
 | 
						|
      # Convert the VyOS config to an abstract internal representation
 | 
						|
      config = ...
 | 
						|
      return config
 | 
						|
 | 
						|
  def verify(config):
 | 
						|
      # Verify that configuration is valid
 | 
						|
      if invalid:
 | 
						|
          raise ConfigError("Descriptive message")
 | 
						|
      return True
 | 
						|
 | 
						|
  def generate(config):
 | 
						|
      # Generate daemon configs
 | 
						|
      pass
 | 
						|
 | 
						|
  def apply(config):
 | 
						|
      # Apply the generated configs to the live system
 | 
						|
      pass
 | 
						|
 | 
						|
  try:
 | 
						|
      config = get_config()
 | 
						|
      verify(config)
 | 
						|
  except ConfigError as e:
 | 
						|
      print(e)
 | 
						|
      sys.exit(1)
 | 
						|
 | 
						|
The **get_config()** function must convert the VyOS config to an abstract internal
 | 
						|
representation. No other function is allowed to call ``vyos.config.Config`` object
 | 
						|
methods directly. The rationale for it is that when config reads are mixed with
 | 
						|
other logic, it's very hard to change the config syntax since you need to weed
 | 
						|
out every occurence of the old syntax. If syntax-specific code is confined to a
 | 
						|
single function, the rest of the code can be left untouched as long as the
 | 
						|
internal representation remains compatible.
 | 
						|
 | 
						|
Another advantage is testability of the code. Mocking the entire config subsystem
 | 
						|
is hard, while constructing an internal representation by hand is way simpler.
 | 
						|
 | 
						|
The **verify()** function takes an internal representation of the config and checks
 | 
						|
if it's valid, otherwise it must raise ``VyOSError`` with an error message that
 | 
						|
describes the problem and possibly suggests how to fix it. It must not make any
 | 
						|
changes to the system. The rationale for it is again testability and, in the
 | 
						|
future when the config backend is ready and every script is rewritten in this
 | 
						|
fashion, ability to execute commit dry run ("commit test" like in JunOS) and abort
 | 
						|
commit before making any changes to the system if an error is found in any component.
 | 
						|
 | 
						|
The **generate()** function generates config files for system components.
 | 
						|
 | 
						|
The **apply()** function applies the generated configuration to the live system.
 | 
						|
It should use non-disruptive reload whenever possible. It may execute disruptive
 | 
						|
operations such as daemon process restart if a particular component does not
 | 
						|
support non-disruptive reload, or when the expected service degradation is minimal
 | 
						|
(for example, in case of auxillary services such as LLDPd). In case of high impact
 | 
						|
services such as VPN daemon and routing protocols, when non-disruptive reload is
 | 
						|
supported for some but not all types of configuration changes, scripts authors
 | 
						|
should make effort to determine if a configuration change can be done in a
 | 
						|
non-disruptive way and only resort to disruptive restart if it cannot be avoided.
 | 
						|
 | 
						|
Unless absolutely necessary, configuration scripts should not modify the active
 | 
						|
configuration of system components directly. Whenever at all possible, scripts
 | 
						|
should generate a configuration file or files that can be applied with a single
 | 
						|
command such as reloading a service through systemd init. Inserting statements
 | 
						|
one by one is particularly discouraged, for example, when configuring netfilter
 | 
						|
rules, saving them to a file and loading it with iptables-restore should always
 | 
						|
be preferred to executing iptables directly.
 | 
						|
 | 
						|
The **apply()** and **generate()** functions may ``raise ConfigError`` if, for
 | 
						|
example, the daemon failed to start with the updated config. It shouldn't be a
 | 
						|
substitute for proper config checking in the **verify()** function. All reasonable
 | 
						|
effort should be made to verify that generated configuration is valid and will
 | 
						|
be accepted by the daemon, including, when necessary, cross-checks with other
 | 
						|
VyOS configuration subtrees.
 | 
						|
 | 
						|
Exceptions, including ``VyOSError`` (which is raised by ``vyos.config.Config`` on
 | 
						|
improper config operations, such as trying to use ``list_nodes()`` on a non-tag
 | 
						|
node) should not be silenced or caught and re-raised as config error. Sure this
 | 
						|
will not look pretty on user's screen, but it will make way better bug reports,
 | 
						|
and help users (and most VyOS users are IT professionals) do their own debugging
 | 
						|
as well.
 | 
						|
 | 
						|
Coding guidelines
 | 
						|
-----------------
 | 
						|
 | 
						|
Language
 | 
						|
********
 | 
						|
 | 
						|
Python 3 **shall** be used. How long can we keep Python 2 alive anyway?
 | 
						|
 | 
						|
No considerations for Python 2 compatibility **should** be taken.
 | 
						|
 | 
						|
Formatting
 | 
						|
**********
 | 
						|
 | 
						|
Tabs **shall not** be used. Every indentation level should be 4 spaces.
 | 
						|
 | 
						|
Text generation
 | 
						|
***************
 | 
						|
 | 
						|
Template processor **should** be used for generating config files. Built-in
 | 
						|
string formatting **may** be used for simple line-oriented formats where every
 | 
						|
line is self-contained, such as iptables rules. Template processor **must** be
 | 
						|
used for structured, multi-line formats such as those used by ISC DHCPd.
 | 
						|
 | 
						|
The default template processor for VyOS code is jinja2.
 | 
						|
 | 
						|
Code policy
 | 
						|
-----------
 | 
						|
 | 
						|
When modifying the source code, remember these rules of the legacy elimination
 | 
						|
campaign:
 | 
						|
 | 
						|
 * No new features in Perl
 | 
						|
 * No old style command definitions
 | 
						|
 * No code incompatible with Python3
 | 
						|
 | 
						|
.. _process: https://blog.vyos.io/vyos-development-digest-10
 | 
						|
.. _vyos-1x: https://github.com/vyos/vyos-1x/blob/current/schema/
 | 
						|
.. _VyConf: https://github.com/vyos/vyconf/blob/master/data/schemata
 | 
						|
 |