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
210
sysadmin/nginx/config/builder/__init__.py
Normal file
210
sysadmin/nginx/config/builder/__init__.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
"""
|
||||
The Builder API defines a pluggable builder framework for manipulating nginx configs from within python.
|
||||
|
||||
Building a config
|
||||
=================
|
||||
|
||||
Every config built using the builder pattern starts off with creating a :class:NginxConfigBuilder::
|
||||
|
||||
from nginx.config.builder import NginxConfigBuilder
|
||||
|
||||
nginx = NginxConfigBuilder()
|
||||
|
||||
By default, this comes loaded with a bunch of helpful tools to easily create routes and servers
|
||||
in nginx::
|
||||
|
||||
with nginx.add_server() as server:
|
||||
server.add_route('/foo').end()
|
||||
with server.add_route('/bar') as bar:
|
||||
bar.add_route('/baz')
|
||||
|
||||
This generates a simple config that looks like this::
|
||||
|
||||
error_log logs/nginx.error.log;
|
||||
worker_processes auto;
|
||||
daemon on;
|
||||
http {
|
||||
include ../conf/mime.types;
|
||||
server {
|
||||
server_name _;
|
||||
location /foo {
|
||||
}
|
||||
location /bar {
|
||||
location /baz {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
Plugins
|
||||
=======
|
||||
|
||||
A plugin is a class that inherits from :class:`nginx.config.builder.baseplugins.Plugin` that provides
|
||||
additional methods which can be chained off of the :class:`NginxConfigBuilder` object. These plugins provide
|
||||
convenience methods that manipulate the underlying nginx configuration that gets built by the
|
||||
:class:`NginxConfigBuilder`.
|
||||
|
||||
A simple plugin only needs to define what methods it's going to export::
|
||||
|
||||
class NoopPlugin(Plugin):
|
||||
name = 'noop'
|
||||
|
||||
@property
|
||||
def exported_methods(self):
|
||||
return {'noop': self.noop}
|
||||
|
||||
def noop(self):
|
||||
pass
|
||||
|
||||
This NoopPlugin provides a simple function that can be called off of a :class:`NginxConfigBuilder` that
|
||||
does nothing successfully. More complex plugins can be found in :mod:`nginx.config.builder.plugins`
|
||||
|
||||
To use this NoopPlugin, we need to create a config builder and then register the plugin with it::
|
||||
|
||||
nginx = NginxConfigBuilder()
|
||||
nginx.noop() # AttributeError :(
|
||||
nginx.register_plugin(NoopPlugin())
|
||||
nginx.noop() # it works!
|
||||
|
||||
A more complex plugin would actually do something, like a plugin that adds an expiry directive to
|
||||
a route::
|
||||
|
||||
class ExpiryPlugin(Plugin):
|
||||
name = 'expiry'
|
||||
@property
|
||||
|
||||
"""
|
||||
|
||||
from ..api import EmptyBlock, Block, Config
|
||||
from .exceptions import ConfigBuilderConflictException, ConfigBuilderException, ConfigBuilderNoSuchMethodException
|
||||
from .baseplugins import RoutePlugin, ServerPlugin, Plugin
|
||||
|
||||
|
||||
DEFAULT_PLUGINS = (RoutePlugin, ServerPlugin)
|
||||
INVALID_PLUGIN_NAMES = ('top,')
|
||||
|
||||
|
||||
class NginxConfigBuilder(object):
|
||||
""" Helper that builds a working nginx configuration
|
||||
|
||||
Exposes a plugin-based architecture for generating nginx configurations.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, worker_processes='auto', worker_connections=512, error_log='logs/error.log', daemon='off'):
|
||||
"""
|
||||
:param worker_processes str|int: number of worker processes to start with (default: auto)
|
||||
:param worker_connections int: number of nginx worker connections (default: 512)
|
||||
:param error_log str: path to nginx error log (default: logs/error.log)
|
||||
:param daemon str: whether or not to daemonize nginx (default: on)
|
||||
"""
|
||||
|
||||
self.plugins = []
|
||||
self._top = EmptyBlock(
|
||||
worker_processes=worker_processes,
|
||||
error_log=error_log,
|
||||
daemon=daemon,
|
||||
)
|
||||
|
||||
self._http = Block(
|
||||
'http',
|
||||
include='../conf/mime.types'
|
||||
)
|
||||
|
||||
self._cwo = self._http
|
||||
self._events = Block(
|
||||
'events',
|
||||
worker_connections=worker_connections
|
||||
)
|
||||
|
||||
self._methods = {}
|
||||
|
||||
for plugin in DEFAULT_PLUGINS:
|
||||
self.register_plugin(plugin(parent=self._http))
|
||||
|
||||
def _validate_plugin(self, plugin):
|
||||
if not isinstance(plugin, Plugin):
|
||||
raise ConfigBuilderException(
|
||||
"Must be a subclass of {cls}".format(cls=Plugin.__name__),
|
||||
plugin=plugin
|
||||
)
|
||||
|
||||
if plugin.name in INVALID_PLUGIN_NAMES:
|
||||
raise ConfigBuilderException(
|
||||
"{name} is a protected name and cannot be used as the name of"
|
||||
" a plugin".format(name=plugin.name),
|
||||
plugin=plugin
|
||||
)
|
||||
|
||||
if plugin.name in (loaded.name for loaded in self.plugins):
|
||||
raise ConfigBuilderConflictException(plugin=plugin, loaded_plugin=plugin, method_name='name')
|
||||
|
||||
methods = plugin.exported_methods.items()
|
||||
|
||||
# check for conflicts
|
||||
for loaded_plugin in self.plugins:
|
||||
for (name, method) in methods:
|
||||
if name in loaded_plugin.exported_methods:
|
||||
raise ConfigBuilderConflictException(
|
||||
plugin=plugin,
|
||||
loaded_plugin=loaded_plugin,
|
||||
method_name=name
|
||||
)
|
||||
|
||||
# Also protect register_plugin, etc.
|
||||
if hasattr(self, name):
|
||||
raise ConfigBuilderConflictException(
|
||||
plugin=plugin,
|
||||
loaded_plugin='top',
|
||||
method_name=name
|
||||
)
|
||||
|
||||
# we can only be owned once
|
||||
if plugin._config_builder:
|
||||
raise ConfigBuilderException("Already owned by another NginxConfigBuilder", plugin=plugin)
|
||||
plugin._config_builder = self
|
||||
|
||||
def register_plugin(self, plugin):
|
||||
""" Registers a new nginx builder plugin.
|
||||
|
||||
Plugins must inherit from nginx.builder.baseplugins.Plugin and not expose methods that conflict
|
||||
with already loaded plugins
|
||||
|
||||
:param plugin nginx.builder.baseplugins.Plugin: nginx plugin to add to builder
|
||||
"""
|
||||
|
||||
self._validate_plugin(plugin)
|
||||
|
||||
# insert ourselves as the config builder for plugins
|
||||
plugin._config_builder = self
|
||||
self.plugins.append(plugin)
|
||||
|
||||
self._methods.update(plugin.exported_methods)
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
""" Returns the logical top of the config hierarchy.
|
||||
|
||||
This is a convenience method for any plugins that need to quickly access the top of the config tree.
|
||||
|
||||
:returns :class:`nginx.config.Block`: Top of the config block
|
||||
"""
|
||||
return self._http
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# Since we want this to be easy to use, we will do method lookup
|
||||
# on the methods that we've gotten from our different config plugins
|
||||
# whenever someone tries to call a method off of the builder.
|
||||
#
|
||||
# This means that plugins can just return a reference to the builder
|
||||
# so that users can just chain methods off of the builder.
|
||||
try:
|
||||
return self._methods[attr]
|
||||
except KeyError:
|
||||
raise ConfigBuilderNoSuchMethodException(attr, builder=self)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(Config(self._top, self._events, self._http))
|
Loading…
Add table
Add a link
Reference in a new issue