Add a copy of the nginx builder module
This commit is contained in:
parent
ee9e06b1ee
commit
261e9d9fbb
14 changed files with 1237 additions and 0 deletions
65
sysadmin/nginx/config/api/__init__.py
Normal file
65
sysadmin/nginx/config/api/__init__.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
The Block API provides objects to programatically generate nginx configurations.
|
||||
|
||||
Example::
|
||||
|
||||
>>> from nginx.config.api import Config, Section, Location
|
||||
>>> events = Section('events', worker_connections='1024')
|
||||
>>> http = Section('http', include='../conf/mime.types')
|
||||
>>> http.sections.add(
|
||||
... Section(
|
||||
... 'server',
|
||||
... Location(
|
||||
... '/foo',
|
||||
... proxy_pass='upstream',
|
||||
... ),
|
||||
... server_name='_',
|
||||
... )
|
||||
... )
|
||||
>>> nginx = Config(
|
||||
... events,
|
||||
... http,
|
||||
... worker_processes='auto',
|
||||
... daemon='on',
|
||||
... error_log='var/error.log',
|
||||
... )
|
||||
>>> print(nginx)
|
||||
|
||||
error_log var/error.log;
|
||||
worker_processes auto;
|
||||
daemon on;
|
||||
http {
|
||||
include ../conf/mime.types;
|
||||
server {
|
||||
server_name _;
|
||||
location /foo {
|
||||
proxy_pass upstream;
|
||||
}
|
||||
}
|
||||
}
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
.. The objects in this submodule are largely inspired by code found in https://github.com/FeroxTL/pynginxconfig-new.
|
||||
|
||||
"""
|
||||
from .blocks import EmptyBlock, Block, Location
|
||||
from .options import Comment, KeyOption, KeyValueOption, KeyMultiValueOption
|
||||
|
||||
__all__ = [
|
||||
'EmptyBlock',
|
||||
'Block',
|
||||
'Location',
|
||||
'KeyOption',
|
||||
'KeyValueOption',
|
||||
'KeyValueMultilines',
|
||||
'KeyMultiValueOption',
|
||||
'Comment',
|
||||
'Config',
|
||||
'Section'
|
||||
]
|
||||
|
||||
# aliases
|
||||
Config = EmptyBlock
|
||||
Section = Block
|
22
sysadmin/nginx/config/api/base.py
Normal file
22
sysadmin/nginx/config/api/base.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Base(object):
|
||||
""" This is the base class for all blocks and options. """
|
||||
_indent_level = 0
|
||||
_indent_char = ' '
|
||||
_indent = 4
|
||||
_parent = None
|
||||
|
||||
def _get_indent(self):
|
||||
return self._indent_char * self._indent * self._indent_level
|
||||
|
||||
def _render(self, name):
|
||||
return '\n{indent}{name}'.format(
|
||||
name=name,
|
||||
indent=self._get_indent()
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__repr__())
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
121
sysadmin/nginx/config/api/blocks.py
Normal file
121
sysadmin/nginx/config/api/blocks.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
import six
|
||||
|
||||
from .base import Base
|
||||
from .options import AttrDict, AttrList, KeyOption, KeyValueOption, KeyMultiValueOption
|
||||
|
||||
|
||||
class Block(Base):
|
||||
""" A block represent a named section of an Nginx config, such as 'http', 'server' or 'location'
|
||||
|
||||
Using this object is as simple as providing a name and any sections or options,
|
||||
which can be other Block objects or option objects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> from nginx.config.api import Block
|
||||
>>> http = Block('http', option='value')
|
||||
>>> print(http)
|
||||
|
||||
http {
|
||||
option value;
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
def __init__(self, name, *sections, **options):
|
||||
""" Creates a block.
|
||||
|
||||
Sections should be config objects such as Block or EmptyBlock,
|
||||
Options can be any key/value pair (such as worker_connections=512, etc)
|
||||
|
||||
:param name str: The name of the block.
|
||||
"""
|
||||
self.name = name
|
||||
self.sections = AttrList(self)
|
||||
self.options = AttrDict(self)
|
||||
|
||||
self._set_directives(*sections, **options)
|
||||
|
||||
@property
|
||||
def _directives(self):
|
||||
dirs = self._dump_options() + list(self.sections)
|
||||
return [directive for directive in dirs if directive is not self]
|
||||
|
||||
def _set_directives(self, *sections, **options):
|
||||
for section in sections:
|
||||
self.sections.append(section)
|
||||
for key, value in six.iteritems(options):
|
||||
setattr(self.options, key, value)
|
||||
|
||||
def _build_options(self, key, value):
|
||||
if isinstance(value, Block):
|
||||
option = value
|
||||
elif isinstance(value, list):
|
||||
option = KeyMultiValueOption(key, value=value)
|
||||
elif value is None or value == '':
|
||||
option = KeyOption(key)
|
||||
else:
|
||||
if isinstance(value, str) and ' ' in value:
|
||||
option = KeyMultiValueOption(key, value=value.split())
|
||||
else:
|
||||
option = KeyValueOption(key, value=value)
|
||||
return option
|
||||
|
||||
def _dump_options(self):
|
||||
return [self._build_options(key, value) for key, value in six.iteritems(self.options)]
|
||||
|
||||
def __repr__(self):
|
||||
directives = self._directives
|
||||
|
||||
for directive in directives:
|
||||
if directive is not self:
|
||||
directive._indent_level = self._indent_level + 1
|
||||
|
||||
return '\n{indent}{name}{{{directives}\n{indent}}}'.format(
|
||||
name='{0} '.format(self.name),
|
||||
directives=''.join([repr(e) for e in directives]),
|
||||
indent=self._get_indent(),
|
||||
)
|
||||
|
||||
|
||||
class EmptyBlock(Block):
|
||||
""" An unnamed block of options and/or sections.
|
||||
|
||||
Empty blocks are useful for representing groups of options.
|
||||
|
||||
For example, you can use them to represent options with non-unique keys:
|
||||
|
||||
Example::
|
||||
|
||||
>>> from nginx.config.helpers import duplicate_options
|
||||
>>> dupes = duplicate_options('key', ['value', 'other_value', 'third_value'])
|
||||
>>> type(dupes)
|
||||
nginx.config.api.blocks.EmptyBlock
|
||||
>>> print(dupes)
|
||||
|
||||
key third_value;
|
||||
key value;
|
||||
key other_value;
|
||||
|
||||
|
||||
"""
|
||||
def __init__(self, *sections, **options):
|
||||
""" Create an EmptyBlock. """
|
||||
self.sections = AttrList(self)
|
||||
self.options = AttrDict(self)
|
||||
|
||||
self._set_directives(*sections, **options)
|
||||
|
||||
def __repr__(self):
|
||||
directives = self._directives
|
||||
|
||||
for directive in directives:
|
||||
directive._indent_level = self._indent_level
|
||||
|
||||
return ''.join([repr(o) for o in directives])
|
||||
|
||||
|
||||
class Location(Block):
|
||||
""" A Location is just a named block with "location" prefixed """
|
||||
def __init__(self, location, *args, **kwargs):
|
||||
super(Location, self).__init__('location {0}'.format(location), *args, **kwargs)
|
139
sysadmin/nginx/config/api/options.py
Normal file
139
sysadmin/nginx/config/api/options.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
from .base import Base
|
||||
|
||||
|
||||
class KeyOption(Base):
|
||||
""" A KeyOption represents a directive with no value.
|
||||
|
||||
For example: http://nginx.org/en/docs/http/ngx_http_core_module.html#internal
|
||||
|
||||
"""
|
||||
def __init__(self, name):
|
||||
self.name = self.value = name
|
||||
|
||||
def __repr__(self):
|
||||
return self._render(
|
||||
'{name};'.format(
|
||||
name=self.name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KeyValueOption(Base):
|
||||
""" A key/value directive. This covers most directives available for Nginx """
|
||||
def __init__(self, name, value=''):
|
||||
self.name = name
|
||||
if isinstance(value, bool):
|
||||
self.value = 'off' if value is False else 'on'
|
||||
elif isinstance(value, int):
|
||||
self.value = str(value)
|
||||
elif isinstance(value, list):
|
||||
self.value = [str(e) for e in value]
|
||||
else:
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return self._render(
|
||||
'{name} {value};'.format(
|
||||
name=self.name,
|
||||
value=self.value
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KeyMultiValueOption(KeyValueOption):
|
||||
""" A key with multiple space delimited options.
|
||||
|
||||
For example: http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
|
||||
|
||||
Example::
|
||||
|
||||
>>> from nginx.config.api.options import KeyMultiValueOption
|
||||
>>> a_log = KeyMultiValueOption('access_log', ['/path/to/log.gz', 'combined', 'gzip', 'flush=5m'])
|
||||
>>> print(a_log)
|
||||
|
||||
access_log /path/to/log.gz combined gzip flush=5m;
|
||||
|
||||
"""
|
||||
def __repr__(self):
|
||||
return self._render(
|
||||
'{name} {value};'.format(
|
||||
name=self.name,
|
||||
value=' '.join(self.value)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class KeyValuesMultiLines(Base):
|
||||
def __init__(self, name, values=[]):
|
||||
self.name = name
|
||||
self.lines = []
|
||||
for value in values:
|
||||
if isinstance(value, list):
|
||||
self.lines.append(' '.join([str(v) for v in value]))
|
||||
else:
|
||||
self.lines.append(str(value))
|
||||
|
||||
def __repr__(self):
|
||||
return ''.join(
|
||||
[self._render('{name} {value};'.format(name=self.name, value=line)) for line in self.lines]
|
||||
)
|
||||
|
||||
|
||||
class Comment(Base):
|
||||
""" A simple comment object. """
|
||||
|
||||
_offset = ''
|
||||
_comment = ''
|
||||
|
||||
def __init__(self, offset='', comment='', **kwargs):
|
||||
self._offset = offset
|
||||
self._comment = comment
|
||||
super(Comment, self).__init__(**kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return self._render(
|
||||
'{offset}# {comment}'.format(
|
||||
offset=self._offset,
|
||||
comment=self._comment,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AttrDict(dict):
|
||||
""" A dictionary that exposes it's values as attributes. """
|
||||
def __init__(self, owner):
|
||||
self.__dict__ = self
|
||||
self._owner = owner
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
if hasattr(val, '_parent'):
|
||||
val._parent = self._owner
|
||||
return super(AttrDict, self).__setitem__(key, val)
|
||||
|
||||
def __repr__(self):
|
||||
owner = self.pop('_owner')
|
||||
ret = super(AttrDict, self).__repr__()
|
||||
self._owner = owner
|
||||
return ret
|
||||
|
||||
|
||||
class AttrList(AttrDict):
|
||||
""" A dictionary/list hybrid that exposes values as attributes. """
|
||||
def __iter__(self):
|
||||
return iter(self.values())
|
||||
|
||||
def append(self, item):
|
||||
if hasattr(item, '_parent'):
|
||||
item._parent = self._owner
|
||||
if hasattr(item, 'name'):
|
||||
self[item.name] = item
|
||||
else:
|
||||
self[hash(item)] = item
|
||||
|
||||
def add(self, *items):
|
||||
for item in items:
|
||||
self.append(item)
|
||||
|
||||
|
||||
# alias for backwards compatibility
|
||||
KeyValuesMultilines = KeyValuesMultiLines
|
Loading…
Add table
Add a link
Reference in a new issue