diff --git a/CHANGES.rst b/CHANGES.rst index 3b00d88d0..3dd62d670 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,11 +1,21 @@ Change log ========== -Version 0.6.2 [unreleased] +Version 0.6.3 [unreleased] -------------------------- WIP +Version 0.6.2 [2017-08-29] +-------------------------- + +- `#78 `_ + [base] Added support for multiple renderers +- `#94 `_ + [schema] Made ``bssid`` not required for wireless stations +- `#97 `_ + [python2] Fixed ``py2-ipaddress`` related unicode bug + Version 0.6.1 [2017-07-05] -------------------------- diff --git a/README.rst b/README.rst index a4cc60aae..c76f0b4fe 100644 --- a/README.rst +++ b/README.rst @@ -13,10 +13,14 @@ netjsonconfig .. image:: https://badge.fury.io/py/netjsonconfig.svg :target: http://badge.fury.io/py/netjsonconfig + +.. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square + :target: https://gitter.im/openwisp/general ------------ -Netjsonconfig is part of the `OpenWISP project `_. +Netjsonconfig is part of the `OpenWISP project `_ and it's the official +configuration engine of `OpenWISP 2 `_. .. image:: http://netjsonconfig.openwisp.org/en/latest/_images/openwisp.org.svg :target: http://openwisp.org diff --git a/bin/netjsonconfig b/bin/netjsonconfig index 331d0325d..116bce2c9 100644 --- a/bin/netjsonconfig +++ b/bin/netjsonconfig @@ -1,9 +1,11 @@ #!/usr/bin/env python +import argparse import os import sys + import six -import argparse + import netjsonconfig import traceback @@ -57,7 +59,7 @@ output = parser.add_argument_group('output') output.add_argument('--backend', '-b', required=True, - choices=['openwrt', 'openwisp', 'openvpn'], + choices=netjsonconfig.get_backends().keys(), action='store', type=str, help='Configuration backend') @@ -167,13 +169,8 @@ context = dict(os.environ) method = args.method method_arguments = parse_method_arguments(args.args) -backends = { - 'openwrt': netjsonconfig.OpenWrt, - 'openwisp': netjsonconfig.OpenWisp, - 'openvpn': netjsonconfig.OpenVpn -} -backend_class = backends[args.backend] +backend_class = netjsonconfig.get_backends()[args.backend] try: options = dict(templates=templates, context=context) if args.config: diff --git a/docs/source/backends/create_your_backend.rst b/docs/source/backends/create_your_backend.rst new file mode 100644 index 000000000..a6719889e --- /dev/null +++ b/docs/source/backends/create_your_backend.rst @@ -0,0 +1,96 @@ + +=================== +Create your backend +=================== + +.. include:: ../_github.rst + +Every backend is based on the common ground of some elements provided by the +netjsonconfig library. The `BaseBackend`, `BaseConverter`, `BaseParser` and +`BaseRenderer` are a battle proven set of tools that can be extended when +creating you backend. + +But the netjsonconfig package is not a playground to experiment, your contributions +to a new backend should start elsewhere, a different package, where you are in control +and can make errors and experiment more. + +Netjsonconfig can now discover packages that provides a custom backend using +a feature available in the Python packaging ecosystem which is called `entry_points`. + +To create a new backend start from scratch with a new folder and add this file to your +project root directory. + +.. code-block:: python + + # setup.py + from setuptools import setup, find_packages + + setup( + name='example_backend', + version='0.0.0', + description='an example to illustrate a netjsonconfig backend as an external module', + packages=find_packages(), + entry_points={ + 'netjsonconfig.backends': [ + 'example=example_backend.__init__:ExampleBackend', + ] + } + ) + +this file can be used to create a package that can be installed using pip or other tools +in the python ecosystem. You can find more information about Python packaging +`at packaging.python.org `_ +and `at the hitchhikers guide to packaging `_. + +The most important part is to give your package a good name, a well thought description and +to add the `entry_points` keyword argument with the following code + +.. code-block:: python + + { + # this is used by netjsonconfig + # to find your backend + 'netjsonconfig.backends': [ + ... + ] + } + +Now your package will be in the list of backends that netjsonconfig can use! + +But we still have to give us a name to be unique! Netjsonconfig already +defined the names `openwisp`, `openwrt` and `openvpn` but you can choose +whatever you like most. + +The name `netjsonconfig.backends` will be associated with a list of classes +from your package that will be presented to netjconfig at runtime. To specify +which classes you want to expose write the triple `name`, `path` and `class_name` +using the format `name=path:class_name` as in the example below. + +The `path` part is simply the path to the file that contains the class +you want to expose and the `class_name` is the name of the class. + +.. code-block:: python + + { + 'netjsonconfig.backends': [ + # name=path:class_name + 'example=example_backend.__init__:ExampleBackend', + ] + } + +The previous example can be used with the following class definition + +.. code-block:: python + + # example_backend/__init__.py + from netjsonconfig.backends.base.backend import BaseBackend + from netjsonconfig.backends.base.renderer import BaseRenderer + from netjsonconfig.backends.base.parser import BaseParser + + from netjsonconfig.schema import schema as default_schema + + class ExampleBackend(BaseBackend): + schema = default_schema + converter = [] + parser = BaseParser + renderer = BaseRenderer diff --git a/docs/source/index.rst b/docs/source/index.rst index 110b4c96b..bc40f2371 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,11 @@ netjsonconfig .. image:: https://badge.fury.io/py/netjsonconfig.svg :target: http://badge.fury.io/py/netjsonconfig -Netjsonconfig is part of the `OpenWISP project `_. +.. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square + :target: https://gitter.im/openwisp/general + +Netjsonconfig is part of the `OpenWISP project `_ and it's the official +configuration engine of `OpenWISP 2 `_. .. image:: ./images/openwisp.org.svg :target: http://openwisp.org @@ -51,6 +55,7 @@ Contents: /backends/openwrt /backends/openwisp /backends/openvpn + /backends/create_your_backend /general/commandline_utility /general/running_tests /general/contributing diff --git a/netjsonconfig/__init__.py b/netjsonconfig/__init__.py index 7e5e3c872..feac19d4f 100644 --- a/netjsonconfig/__init__.py +++ b/netjsonconfig/__init__.py @@ -1,5 +1,24 @@ +from pkg_resources import iter_entry_points +import logging + from .version import VERSION, __version__, get_version # noqa from .backends.openwrt.openwrt import OpenWrt # noqa from .backends.openwisp.openwisp import OpenWisp # noqa from .backends.openvpn.openvpn import OpenVpn # noqa + + +def get_backends(): + default = { + 'openwrt': OpenWrt, + 'openwisp': OpenWisp, + 'openvpn': OpenVpn, + } + logger = logging.getLogger(__name__) + + for entry_point in iter_entry_points('netjsonconfig.backends'): + try: + default.update({entry_point.name.lower(): entry_point.load()}) + except ImportError as e: # noqa + logger.error("Error loading backend {}".format(entry_point.name.lower())) + return default diff --git a/netjsonconfig/backends/openwrt/converters/interfaces.py b/netjsonconfig/backends/openwrt/converters/interfaces.py index 3ead69d65..d2f712e4b 100644 --- a/netjsonconfig/backends/openwrt/converters/interfaces.py +++ b/netjsonconfig/backends/openwrt/converters/interfaces.py @@ -68,7 +68,7 @@ def __intermediate_addresses(self, interface): # do not use CIDR notation when using a single ipv4 # see https://github.com/openwisp/netjsonconfig/issues/54 if len(static.get('ipaddr', [])) == 1: - network = ip_interface(static['ipaddr'][0]) + network = ip_interface(six.text_type(static['ipaddr'][0])) static['ipaddr'] = str(network.ip) static['netmask'] = str(network.netmask) # do not use lists when using a single ipv6 address @@ -269,7 +269,7 @@ def __netjson_addresses(self, interface): return interface def __netjson_address(self, address, interface): - ip = ip_interface(address) + ip = ip_interface(six.text_type(address)) family = 'ipv{0}'.format(ip.version) netjson = OrderedDict(( ('address', str(ip.ip)), @@ -279,7 +279,7 @@ def __netjson_address(self, address, interface): )) uci_gateway_key = 'gateway' if family == 'ipv4' else 'ip6gw' gateway = interface.get(uci_gateway_key, None) - if gateway and ip_address(gateway) in ip.network: + if gateway and ip_address(six.text_type(gateway)) in ip.network: netjson['gateway'] = gateway del interface[uci_gateway_key] return netjson diff --git a/netjsonconfig/backends/openwrt/converters/routes.py b/netjsonconfig/backends/openwrt/converters/routes.py index bdb891457..829d1837f 100644 --- a/netjsonconfig/backends/openwrt/converters/routes.py +++ b/netjsonconfig/backends/openwrt/converters/routes.py @@ -1,5 +1,7 @@ from ipaddress import ip_interface +import six + from ..schema import schema from .base import OpenWrtConverter @@ -16,7 +18,7 @@ def to_intermediate_loop(self, block, result, index=None): return result def __intermediate_route(self, route, index): - network = ip_interface(route.pop('destination')) + network = ip_interface(six.text_type(route.pop('destination'))) target = network.ip if network.version == 4 else network.network route.update({ '.type': 'route{0}'.format('6' if network.version == 6 else ''), @@ -50,7 +52,7 @@ def __netjson_route(self, route, i): network = '{0}/{1}'.format(network, route.pop('netmask')) route.update({ "device": route.pop('interface'), - "destination": str(ip_interface(network)), + "destination": str(ip_interface(six.text_type(network))), "next": route.pop('gateway'), "cost": route.pop('metric', self._schema['properties']['cost']['default']) }) diff --git a/netjsonconfig/backends/openwrt/converters/rules.py b/netjsonconfig/backends/openwrt/converters/rules.py index 7484605d7..7d508b6df 100644 --- a/netjsonconfig/backends/openwrt/converters/rules.py +++ b/netjsonconfig/backends/openwrt/converters/rules.py @@ -1,5 +1,7 @@ from ipaddress import ip_network +import six + from ..schema import schema from .base import OpenWrtConverter @@ -21,9 +23,9 @@ def __intermediate_rule(self, rule, index): dest_net = None family = 4 if 'src' in rule: - src_net = ip_network(rule['src']) + src_net = ip_network(six.text_type(rule['src'])) if 'dest' in rule: - dest_net = ip_network(rule['dest']) + dest_net = ip_network(six.text_type(rule['dest'])) if dest_net or src_net: family = dest_net.version if dest_net else src_net.version rule.update({ diff --git a/netjsonconfig/backends/openwrt/schema.py b/netjsonconfig/backends/openwrt/schema.py index 084370caa..7aeaadcb9 100644 --- a/netjsonconfig/backends/openwrt/schema.py +++ b/netjsonconfig/backends/openwrt/schema.py @@ -6,7 +6,6 @@ from ..openvpn.schema import base_openvpn_schema from .timezones import timezones - default_radio_driver = "mac80211" diff --git a/netjsonconfig/version.py b/netjsonconfig/version.py index dfe5fc50a..d87ba46b4 100644 --- a/netjsonconfig/version.py +++ b/netjsonconfig/version.py @@ -1,4 +1,4 @@ -VERSION = (0, 6, 2, 'alpha') +VERSION = (0, 6, 3, 'alpha') __version__ = VERSION diff --git a/requirements.txt b/requirements.txt index e7001cba2..9769d12bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -jinja2 -jsonschema +jinja2>=2.9,<3.0 +jsonschema>=2.6,<2.7 six diff --git a/setup.py b/setup.py index 1e307cbe8..fbbc7b241 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ extras_require = { # used for wheel package, # see http://wheel.readthedocs.io/en/latest/#defining-conditional-dependencies - ':python_version in "2.6 2.7"' : ['py2-ipaddress'] + ':python_version in "2.6 2.7"': ['py2-ipaddress'] } diff --git a/tests/openwisp/test_backend.py b/tests/openwisp/test_backend.py index d7fbd08d2..6b33c0fe7 100644 --- a/tests/openwisp/test_backend.py +++ b/tests/openwisp/test_backend.py @@ -211,9 +211,9 @@ def test_tc_script(self): self.assertIn('tc class add dev tap0 parent 1 classid 1:1 htb rate 1024kbit burst 191k', contents) self.assertIn('tc class add dev tap0 parent 1:1 classid 1:2 htb rate 512kbit ceil 1024kbit', contents) self.assertIn('tc qdisc add dev tap0 ingress', contents) - l = 'tc filter add dev tap0 parent ffff: preference 0 u32 match u32 0x0 0x0 police '\ - 'rate 2048kbit burst 383k drop flowid :1' - self.assertIn(l, contents) + line = 'tc filter add dev tap0 parent ffff: preference 0 u32 match u32 0x0 0x0 police '\ + 'rate 2048kbit burst 383k drop flowid :1' + self.assertIn(line, contents) tar.close() def test_cron(self):