1
0
Fork 0
scripts/sysadmin/nginx/config/builder/baseplugins.py

206 lines
5.5 KiB
Python

import re
import six
from abc import ABCMeta, abstractproperty
from ..api import Block, Location
from .exceptions import ConfigBuilderException
@six.add_metaclass(ABCMeta)
class Navigable(object):
""" Indicates that a class is navigable.
This means that it references some type of nginx config machinery,
and that this is traversable.
"""
_config_builder = None
def __init__(self, *args, **kwargs):
""" Creates a new Navigable class
:param nginx.builder.ConfigBuilder config_builder: internal ConfigBuilder used to create nginx config objs
"""
# This can sometimes be added by direct access,
# in the case of plugins
self._config_builder = kwargs.get('config_builder', None)
self._parent = kwargs.get('parent', None)
def chobj(self, obj):
""" Changes the current working object to the one provided.
:param nginx.config.Block obj: object that we're scoping to
"""
self.config_builder._cwo = obj
@property
def current_obj(self):
""" Returns the current working object.
:returns nginx.config.Block: object that we're currently scoped to
"""
return self.config_builder._cwo
@property
def config_builder(self):
""" Internal config builder.
:returns nginx.builder.ConfigBuilder: the internal ConfigBuilder for manipulating the nginx config
"""
return self._config_builder
def up(self):
""" Traverse up the config hierarchy """
self.chobj(self.current_obj.parent)
def add_child(self, child):
""" Adds a child to the config object
:param nginx.config.Builder child: child to insert into config tree
"""
name = re.split(r'\s+', self.current_obj.name)[0]
if self.valid_cfg_parents and name not in self.valid_cfg_parents:
raise ConfigBuilderException(
'{parent} is not a valid parent for this plugin. Call this off of one of these: {valid_parents}'.format(
parent=name if name else 'top',
valid_parents=self.valid_cfg_parents
), plugin=self._get_name()
)
self.current_obj.sections.add(child)
def __getattr__(self, attr):
return getattr(self.config_builder, attr)
@abstractproperty
def valid_cfg_parents(self):
return None
@property
def parent(self):
return self._parent
def _get_name(self):
return getattr(self, 'name', self.current_obj.name)
@six.add_metaclass(ABCMeta)
class Plugin(Navigable):
""" Plugin base class. All plugins must inherit from this
Defines a few properties that must be defined:
name - name of the plugin. must be unique per config builder
exported_methods - dict of method names -> callables. method names don't need to match
callables. exported method names must be unique
"""
@abstractproperty
def name(self):
return ''
@abstractproperty
def exported_methods(self):
return {}
@property
def http(self):
return self.config_builder._http
def __str__(self):
return self.name
@six.add_metaclass(ABCMeta)
class Endable(Navigable):
""" Role that adds an `end` convenience method to close scoped blocks (location, server, et al) """
def end(self):
self.up()
return self.config_builder
@six.add_metaclass(ABCMeta)
class Routable(Navigable):
""" A thing that can have routes attached to it.
In nginx, routes can either be attached to server blocks or other routes
"""
def add_route(self, path, *args, **kwargs):
loc = Location(path, *args, **kwargs)
self.add_child(loc)
self.chobj(loc)
return RouteWrapper(self.current_obj, self.config_builder)
@six.add_metaclass(ABCMeta)
class Wrapper(object):
def __getattr__(self, attr):
return getattr(self.config_builder, attr)
class RouteWrapper(Routable, Wrapper, Endable):
""" This needs to wrap routes emitted by this interface since we can nest routes in nginx configs.
This also enables users to use the returned routes/servers as a context manager, so that we can
sugar-coat syntax even more than we already do
"""
valid_cfg_parents = ('server', 'location')
def __init__(self, wrapped, config_builder):
super(RouteWrapper, self).__init__(
parent=wrapped,
config_builder=config_builder
)
def __enter__(self):
return self
def __exit__(self, typ, val, tb):
self.end()
class RoutePlugin(Plugin, Routable, Endable):
""" A plugin that creates routes
Routes can be nested infinitely.
Must be called off of either a server or a location block
"""
name = 'route'
valid_cfg_parents = ('location', 'server')
@property
def exported_methods(self):
return {
'add_route': self.add_route,
}
class ServerPlugin(Plugin, Routable, Endable):
""" A plugin that creates server blocks
Must only be called off of an http block
"""
name = 'server'
valid_cfg_parents = ('http',)
# XXX: add more server options
def add_server(self, hostname='_', **kwargs):
server = Block('server', server_name=hostname, **kwargs)
self.add_child(server)
self.chobj(server)
return RouteWrapper(self.current_obj, self.config_builder)
@property
def exported_methods(self):
return {
'add_server': self.add_server,
'end': self.end
}