📜 ⬆️ ⬇️

Editing Configs in Python



Have you ever had to parse and programmatically change other people's configuration files? And in files with abnormal formats like that of NSD or BIND9? And if the format provides for line breaks, semantic indents and the preservation of comments, the task quickly leaves the trivial category.

This is why I am sharing the python-reconfigure library with you.

')
The library provides object mapping between the text of the config file and python objects.
Reconfigure never “breaks” files, and does not shy away from unfamiliar blocks and options inside, and also saves comments.

Immediately proceed to the example:

>>> from reconfigure.configs import FSTabConfig >>> from reconfigure.items.fstab import FilesystemData >>> >>> config = FSTabConfig(path='/etc/fstab') >>> config.load() >>> print config.tree { "filesystems": [ { "passno": "0", "device": "proc", "mountpoint": "/proc", "freq": "0", "type": "proc", "options": "nodev,noexec,nosuid" }, { "passno": "1", "device": "UUID=dfccef1e-d46c-45b8-969d-51391898c55e", "mountpoint": "/", "freq": "0", "type": "ext4", "options": "errors=remount-ro" } ] } >>> tmpfs = FilesystemData() >>> tmpfs.mountpoint = '/srv/cache' >>> tmpfs.type = 'tmpfs' >>> tmpfs.device = 'none' >>> config.tree.filesystems.append(tmpfs) >>> config.save() >>> quit() $ cat /etc/fstab proc /proc proc nodev,noexec,nosuid 0 0 UUID=dfccef1e-d46c-45b8-969d-51391898c55e / ext4 errors=remount-ro 0 1 none /srv/cache tmpfs none 0 0 


Reconfigure is a modular system, and the * Config classes hide some internal logic.
Consider how the previous example works under the hood.
First, the text of the file is converted by the parser into an abstract syntax tree.

 >>> from reconfigure.parsers import SSVParser >>> from reconfigure.builders import BoundBuilder >>> content = open('/etc/fstab').read() >>> syntax_tree = SSVParser().parse(content) >>> syntax_tree <reconfigure.nodes.RootNode object at 0x7f1319eeec50> >>> print syntax_tree (None) (line) (token) value = proc (token) value = /proc (token) value = proc (token) value = nodev,noexec,nosuid (token) value = 0 (token) value = 0 (line) (token) value = UUID=83810b56-ef4b-44de-85c8-58dc589aef48 (token) value = / (token) value = ext4 (token) value = errors=remount-ro (token) value = 0 (token) value = 1 


Then, the Builder class creates ordinary python objects and binds them to the syntax tree.

 >>> builder = BoundBuilder(FSTabData) >>> data_tree = builder.build(syntax_tree) >>> print data_tree { "filesystems": [ { "passno": "0", "device": "proc", "mountpoint": "/proc", "freq": "0", "type": "proc", "options": "nodev,noexec,nosuid" }, { "passno": "1", "device": "UUID=83810b56-ef4b-44de-85c8-58dc589aef48", "mountpoint": "/", "freq": "0", "type": "ext4", "options": "errors=remount-ro" } ] } 


In fact, the created objects are proxy-classes, all fields of which are properties, and change the values ​​in the syntax tree as they change.

 >>> syntax_tree.children[0] <reconfigure.nodes.Node object at 0x7f51c63b9f10> >>> print syntax_tree.children[0] (line) (token) value = proc (token) value = /proc (token) value = proc (token) value = nodev,noexec,nosuid (token) value = 0 (token) value = 0 >>> data_tree.filesystems[0].options += ',rw' >>> print syntax_tree.children[0] (line) (token) value = proc (token) value = /proc (token) value = proc (token) value = nodev,noexec,nosuid,rw (token) value = 0 (token) value = 0 


Rules for binding tree elements to class fields are specified in * Data classes.
An example of bindings for data in the /etc/resolv.conf file:

 from reconfigure.nodes import Node, PropertyNode from reconfigure.items.bound import BoundData class ResolvData (BoundData): pass class ItemData (BoundData): def template(self): return Node('line', children=[ Node('token', children=[PropertyNode('value', 'nameserver')]), Node('token', children=[PropertyNode('value', '8.8.8.8')]), ]) ResolvData.bind_collection('items', item_class=ItemData) ItemData.bind_property('value', 'name', path=lambda x: x.children[0]) ItemData.bind_property('value', 'value', path=lambda x: x.children[1]) 


Content, syntax and data tree of this file:

 >>> print open('/etc/resolv.conf').read() # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.0.1 >>> print syntax_tree (None) (line) (Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN) (token) value = nameserver (token) value = 127.0.0.1 >>> print data_tree { "items": [ { "name": "nameserver", "value": "127.0.0.1" } ] } 


In addition, Reconfigure is aware of the presence of include directives in some files, and remembers which file it was in.

Reconfigure is easy to expand with your own parsers, builders and plug-ins.

At the moment, Reconfigure is the heart of Ajenti 1.0 Beta , but more on that next time :)

Github
PYPI
Documentation
DEB and RPM packages are available in the Ajenti repositories.

Source: https://habr.com/ru/post/188248/


All Articles