Package: a2conf
a2confa2conf is python3 module which provides easy way to configure apache2. a2conf used bya2utilspackage, so you may use a2utils as examples.For all examples we will use test config fileexamples/example.confin source code. Useexport PYTHONPATH=.to use module if it's not installed.Create simple apache configsee examples/ex5_create.py for full code:vhost=root.insert('<VirtualHost *:80>')vhost.insert(['# This VirtualHost is auto-generated','ServerName example.com','ServerAlias www.example.com','','DocumentRoot /var/www/example.com/'])Read apache configlist all SSL hosts (see examples/ex2_query.py for full code):root=a2conf.Node(config)forvhostinroot.children('<VirtualHost>'):servername=vhost.first('servername').args# First query method, via first(). Not much fail-safe but short.try:ssl_option=next(vhost.children('sslengine')).args# Second query method, via children()ifssl_option.lower()=='on':print("{}has SSL enabled".format(servername))exceptStopIteration:# No SSL Engine directive in this vhostpassGet VirtualHostroot=a2conf.Node(config)vh1=root.find_vhost('example.com','*:80')vh2=root.find_vhost('www.example.com','*:443')vh3=root.find_vhost('example.example.com')forvhostinroot.yield_vhost('example.com'):do_something(vhost)Save virtualHostIf you will callwrite_file()method of vhost, it will write only this vhost. To save file with all vhosts, use save_file().root=a2conf.Node(config)vh=root.find_vhost('example.com')vh.insert(['CustomLog /var/log/apache2/example.com-access.log'])vh.save_file()Node classPropertiesraw- text line as-is, with all spaces, tabs and with commentscmd- cmd ('ServerName') without args or None (if section)section- section (e.g. 'VirtualHost')args- one text line args to cmd or section. for vhost args could be '*:80', for ServerAlias: 'example.com example.org'name- name of node. cmd if node has cmd, or section name (in brackets) if this is section. e.g. 'ServerName' or
''content- list of child nodes (possible empty). For container sections (VirtualHost) attributecontentis list
of children. For usual commands (e.g. ServerName) - empty list.Methods__init__(self, read=filename, raw=None, parent=None, name=None, path=None, line=None, includes=True)- In most cases you should not need to use
any parameters here exceptincludesandread.readis apache config filename to read. Useincludes=Falseif you wantread_filemethod to ignoreInclude*directives.children(name=None, recursive=None)- Main query method, returns generator for all children  nodes (e.g. for VirtualHost node). Generator is empty if no
children. If name specified, generator will return only nodes with this name (e.g. 'servername' or ''). If recursive is On,
generator will return nested nodes too (e.g. what is inside<IfModule>or<Directory>settings). To get just one first element usenext(node.children('ServerName')). It will raiseStopIterationif node has no such children elements.first(name, recursive=None)- wrapper forchildren(). Returns only first element orNone. Not raising exceptions.read_file(filename)- Reads apache config. Called automatically from__init__if you specifiedreadargument.dump(fh=sys.stdout, depth=0)- dump loaded config in unified format (indented). if fh not specified, just dumps to stdout()write_file(filename)- opens file for writing and dump() to this file.save_file()- saves whole file with this vhost (with changes and with other vhosts)add(child)- add new child node to content of Node after all other nodes (including possible closing
Node</VirtualHost>). Child is Node type, useadd_raw(line)to add raw string.insert(child, after)- smarter thenadd(). Add new child node, place it after last node with nameafter. e.g.:doc_root = Node(raw='DocumentRoot /var/www/site1')
vhost.insert([doc_root], after=['ServerName','ServerAlias'])childis list ofNodes to insert. But a2conf is friendly: if you pass single element, it will be converted to list with this element, and then if any element is string, it will be converted to node. So, this command woks well too:vhost.insert('DocumentRoot /var/www/site1', after=['ServerName','ServerAlias'])Ifafternot specified, or not found, child is appended to end. If specified, method tries to insert new node
after last found node inafter.In this example, new node will be inserted afterServerAlias(if it exists). If not, it will be inserted afterServerName(if it exists). And if all commands listed inafteris not found, node will be inserted as last node, right before closing tag (e.g., right before</VirtualHost>).Returns first inserted child. So you may write code like this:# Create virtualhost for example.net
vhost = root.insert("<VirtualHost *:80>")
vhost.insert('ServerName example.net')delete()- removes this Node from parent (e.g. remove vhost from apache config or remove directive from vhost).
You should not calldelete()while iterating overchildren()(unless you want to delete just one node). Proper usage:# Bad way, it will delete only one node:forninvhost.children('Redirect'):n.delete()# Proper waydeleted=list()forninvhost.children('Redirect'):deleted.append(n)fornindeleted:n.delete()ExamplesJust dump apache configexamples/ex1_dump.pyjust loads config and dumps its structure (without comments) as JSON:#!/usr/bin/env python3importsysimporta2confimportjsonroot=a2conf.Node(sys.argv[1])defsection_dump(node):data=dict()forchinnode.children():ifch.sectionandnotch.section.startswith('/'):ifch.args:key=ch.section+' '+ch.argselse:key=ch.sectiondata[key]=section_dump(ch)elifch.cmd:data[ch.cmd]=ch.argsreturndatadata=section_dump(root)print(json.dumps(data,indent=4))Output:$ examples/ex1_dump.py examples/example.conf
{
    "VirtualHost *:80": {
        "DocumentRoot": "/var/www/example",
        "ServerName": "example.com",
        "ServerAlias": "www.example.com example.com 1.example.com 2.example.com",
        "DirectoryIndex": "index.html index.htm default.htm index.php",
        "Options": "-Indexes +FollowSymLinks"
    },
    "VirtualHost *:443": {
        "DocumentRoot": "/var/www/example",
        "ServerName": "example.com",
        "ServerAlias": "www.example.com 1.example.com 2.example.com secure.example.com",
        "DirectoryIndex": "index.html index.htm default.htm index.php",
        "Options": "-Indexes +FollowSymLinks",
        "SSLEngine": "On",
        "SSLCertificateFile": "/etc/letsencrypt/live/example.com/fullchain.pem",
        "SSLCertificateKeyFile": "/etc/letsencrypt/live/example.com/privkey.pem",
        "SSLCertificateChainFile": "/etc/letsencrypt/live/example.com/chain.pem"
    }
}Note - this is short example just for demo, it's not very good for production: if virtualhost has more then one directive
(e.g.ServerAlias,RewriteRule,RewriteCond), only last one will be used.Queryexamples/ex2_query.pyprint all SSL sites from config:#!/usr/bin/env python3importsysimporta2confroot=a2conf.Node(sys.argv[1])forvhostinroot.children('<VirtualHost>'):servername=vhost.first('servername').args# One query method, via first(). Not much fail-safe but short.try:ssl_option=next(vhost.children('sslengine')).args# Other query method, via children()ifssl_option.lower()=='on':print("{}has SSL enabled".format(servername))exceptStopIteration:# No SSL Engine directive in this vhostcontinueOutput:$ examples/ex2_query.py examples/example.conf
example.com has SSL enabledReplace and deleteexamples/ex3_replace_delete.pydisables SSLEngine directive:#!/usr/bin/env python3importsysimporta2confroot=a2conf.Node(sys.argv[1])forvhostinroot.children('<VirtualHost>'):ifvhost.first('sslengine'):vhost.delete()# Delete SSL vhostelse:# Modify DocumentRootvhost.first('DocumentRoot').args='/var/www/example2'vhost.first('DocumentRoot').suffix='# New DocumentRoot!'# Delete ServerAliasvhost.first('ServerAlias').delete()root.dump()Output:$examples/ex3_replace_delete.pyexamples/example.conf## Example config file for a2conf#<VirtualHost*:80># Non-ssl siteDocumentRoot/var/www/example2# New DocumentRoot!ServerNameexample.com# .... OUR TEST SITE ....DirectoryIndexindex.htmlindex.htmdefault.htmindex.phpOptions-Indexes+FollowSymLinks
</VirtualHost>