#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2020 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "<NAME> <<EMAIL>>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2020 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import re

import netius

from . import proxy_r

class DockerProxyServer(proxy_r.ReverseProxyServer):
    """
    Specialized reverse proxy server that handles many of the
    inner details of the reverse proxy server configuration
    server using environment variables information, making it
    easier for the automatic configuration and association of
    servers to the proxy server in a Docker environment.

    Although an optimal usage implies a Docker context the usage
    of the Docker proxy servers is not limited to such use cases.
    """

    def __init__(self, host_suffixes = [], *args, **kwargs):
        proxy_r.ReverseProxyServer.__init__(self, *args, **kwargs)
        self.load_config(host_suffixes = host_suffixes)
        self._build_docker()

    def on_serve(self):
        proxy_r.ReverseProxyServer.on_serve(self)
        if self.env: self.host_suffixes = self.get_env(
            "HOST_SUFFIXES",
            self.host_suffixes,
            cast = list
        )
        self._build_suffixes()
        self._build_redirect_ssl()

    def _build_docker(self):
        self._build_regex()
        self._build_hosts()
        self._build_alias()
        self._build_passwords()
        self._build_redirect()
        self._build_error_urls()
        self._build_redirect_ssl()

    def _build_regex(self, token = "$", sort = True):
        # retrieves the complete set of configuration values with the
        # regex suffix so that they are going to be used for the creation
        # of the regex rules (as expected)
        linked = netius.conf_suffix("_REGEX")

        # retrieves the complete set of names from the linked items and then
        # in case the sort flag is set sorts their values (proper order)
        names = netius.legacy.keys(linked)
        if sort: names.sort()

        # iterates over the complete set of linked regex values splitting
        # the values around the proper token and adding them to the regex
        for name in names:
            value = linked[name]
            value_s = value.split(token, 1)
            if not len(value_s) == 2: continue
            regex, target = value_s
            if not self._valid_url(target): continue
            rule = (re.compile(regex), target)
            self.regex.append(rule)

    def _build_hosts(self, alias = True):
        # tries to retrieve the complete set of configuration
        # values associated with the port suffix, this represents
        # the possible linked container addresses
        linked = netius.conf_suffix("_PORT")

        # iterates over the linked values, validating them and adding
        # them to the list of registered hosts
        for name, host in netius.legacy.iteritems(linked):
            # retrieves the name part of the configuration name
            # and converts it into lower cased value, note that
            # an extra dashed version is created, so that a proper
            # alias may be created for such naming
            base = name[:-5].lower()
            base_dash = base.replace("_", "-")

            # "builds" the name reference of the service and tries
            # to retrieve it from the configuration, it should exist
            # in case this port value represent a service
            name_ref = base.upper() + "_NAME"
            name_value = netius.conf(name_ref, None)
            if not name_value: continue

            # runs a series of validation on both the base and name
            # value to make sure that this value represents a valid
            # linked service/container
            # linked service/container (valid name reference found)
            if name.endswith("_ENV_PORT"): continue
            if not name.find("_ENV_") == -1: continue
            if base[-1].isdigit() and name_value[-1].isdigit(): continue

            # validates that the provided host is a valid URL value and
            # if that's not the case continues the loop (ignores)
            if not self._valid_url(host): continue

            # replaces the prefix of the reference (assumes HTTP) and
            # then adds the base value to the registered hosts
            host = host.replace("tcp://", "http://")
            host = str(host)
            self.hosts[base] = host

            # validates that the dashed version of the name is not the
            # same as the base one (at least one underscore) and if that's
            # not the case skips the current iteration
            if base == base_dash: continue

            # checks if the alias based registration is enabled and adds
            # the dashed version as an alias for such case or as an host
            # otherwise (static registration)
            if alias: self.alias[base_dash] = base
            else: self.hosts[base_dash] = host

    def _build_alias(self):
        linked = netius.conf_suffix("_ALIAS")
        for name, host in netius.legacy.iteritems(linked):
            base = name[:-6].lower()
            base_dash = base.replace("_", "-")
            self.alias[base] = host
            self.alias[base_dash] = host

    def _build_passwords(self):
        linked = netius.conf_suffix("_PASSWORD")
        for name, password in netius.legacy.iteritems(linked):
            base = name[:-9].lower()
            base_dash = base.replace("_", "-")
            simple_auth = netius.SimpleAuth(password = password)
            self.auth[base] = simple_auth
            self.auth[base_dash] = simple_auth

    def _build_redirect(self):
        linked = netius.conf_suffix("_REDIRECT")
        for name, host in netius.legacy.iteritems(linked):
            base = name[:-9].lower()
            base_dash = base.replace("_", "-")
            self.redirect[base] = host
            self.redirect[base_dash] = host

    def _build_error_urls(self):
        linked = netius.conf_suffix("_ERROR_URL")
        for name, error_url in netius.legacy.iteritems(linked):
            base = name[:-10].lower()
            base_dash = base.replace("_", "-")
            self.error_urls[base] = error_url
            self.error_urls[base_dash] = error_url

    def _build_redirect_ssl(self, alias = True):
        linked = netius.conf_suffix("_REDIRECT_SSL")
        for name, _force in netius.legacy.iteritems(linked):
            base = name[:-13].lower()
            base_dash = base.replace("_", "-")
            self.redirect[base] = (base, "https")
            self.redirect[base_dash] = (base_dash, "https")
            if not alias: continue
            for key, value in netius.legacy.iteritems(self.alias):
                is_match = value in (base, base_dash)
                if not is_match: continue
                self.redirect[key] = (key, "https")

    def _build_suffixes(self, alias = True, redirect = True):
        for host_suffix in self.host_suffixes:
            self.info("Registering %s host suffix" % host_suffix)
            for alias, value in netius.legacy.items(self.alias):
                fqn = alias + "." + str(host_suffix)
                self.alias[fqn] = value
            for name, value in netius.legacy.items(self.hosts):
                fqn = name + "." + str(host_suffix)
                if alias: self.alias[fqn] = name
                else: self.hosts[fqn] = value

    def _valid_url(self, value):
        """
        Verifies that the provided value is a valid URL
        value according to the default parsed.

        A valid URL should contain both a valid scheme and
        a not null and valid hostname.

        :type value: String
        :param value: The value that is going to be validated
        for URL compliance.
        :rtype: bool
        :return: If the provided value qualifies as a URL value.
        """

        value = str(value)
        result = netius.legacy.urlparse(value)
        if not result.scheme: return False
        if not result.hostname: return False
        return True

if __name__ == "__main__":
    server = DockerProxyServer()
    server.serve(env = True)
else:
    __path__ = []

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Hive Netius System
# Copyright (c) 2008-2020 Hive Solutions Lda.
#
# This file is part of Hive Netius System.
#
# Hive Netius System is free software: you can redistribute it and/or modify
# it under the terms of the Apache License as published by the Apache
# Foundation, either version 2.0 of the License, or (at your option) any
# later version.
#
# Hive Netius System is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Apache License for more details.
#
# You should have received a copy of the Apache License along with
# Hive Netius System. If not, see <http://www.apache.org/licenses/>.

__author__ = "<NAME> <<EMAIL>>"
""" The author(s) of the module """

__version__ = "1.0.0"
""" The version of the module """

__revision__ = "$LastChangedRevision$"
""" The revision number of the module """

__date__ = "$LastChangedDate$"
""" The last change date of the module """

__copyright__ = "Copyright (c) 2008-2020 Hive Solutions Lda."
""" The copyright for the module """

__license__ = "Apache License, Version 2.0"
""" The license for the module """

import re

import netius

from . import proxy_r

class DockerProxyServer(proxy_r.ReverseProxyServer):
    """
    Specialized reverse proxy server that handles many of the
    inner details of the reverse proxy server configuration
    server using environment variables information, making it
    easier for the automatic configuration and association of
    servers to the proxy server in a Docker environment.

    Although an optimal usage implies a Docker context the usage
    of the Docker proxy servers is not limited to such use cases.
    """

    def __init__(self, host_suffixes = [], *args, **kwargs):
        proxy_r.ReverseProxyServer.__init__(self, *args, **kwargs)
        self.load_config(host_suffixes = host_suffixes)
        self._build_docker()

    def on_serve(self):
        proxy_r.ReverseProxyServer.on_serve(self)
        if self.env: self.host_suffixes = self.get_env(
            "HOST_SUFFIXES",
            self.host_suffixes,
            cast = list
        )
        self._build_suffixes()
        self._build_redirect_ssl()

    def _build_docker(self):
        self._build_regex()
        self._build_hosts()
        self._build_alias()
        self._build_passwords()
        self._build_redirect()
        self._build_error_urls()
        self._build_redirect_ssl()

    def _build_regex(self, token = "$", sort = True):
        # retrieves the complete set of configuration values with the
        # regex suffix so that they are going to be used for the creation
        # of the regex rules (as expected)
        linked = netius.conf_suffix("_REGEX")

        # retrieves the complete set of names from the linked items and then
        # in case the sort flag is set sorts their values (proper order)
        names = netius.legacy.keys(linked)
        if sort: names.sort()

        # iterates over the complete set of linked regex values splitting
        # the values around the proper token and adding them to the regex
        for name in names:
            value = linked[name]
            value_s = value.split(token, 1)
            if not len(value_s) == 2: continue
            regex, target = value_s
            if not self._valid_url(target): continue
            rule = (re.compile(regex), target)
            self.regex.append(rule)

    def _build_hosts(self, alias = True):
        # tries to retrieve the complete set of configuration
        # values associated with the port suffix, this represents
        # the possible linked container addresses
        linked = netius.conf_suffix("_PORT")

        # iterates over the linked values, validating them and adding
        # them to the list of registered hosts
        for name, host in netius.legacy.iteritems(linked):
            # retrieves the name part of the configuration name
            # and converts it into lower cased value, note that
            # an extra dashed version is created, so that a proper
            # alias may be created for such naming
            base = name[:-5].lower()
            base_dash = base.replace("_", "-")

            # "builds" the name reference of the service and tries
            # to retrieve it from the configuration, it should exist
            # in case this port value represent a service
            name_ref = base.upper() + "_NAME"
            name_value = netius.conf(name_ref, None)
            if not name_value: continue

            # runs a series of validation on both the base and name
            # value to make sure that this value represents a valid
            # linked service/container
            # linked service/container (valid name reference found)
            if name.endswith("_ENV_PORT"): continue
            if not name.find("_ENV_") == -1: continue
            if base[-1].isdigit() and name_value[-1].isdigit(): continue

            # validates that the provided host is a valid URL value and
            # if that's not the case continues the loop (ignores)
            if not self._valid_url(host): continue

            # replaces the prefix of the reference (assumes HTTP) and
            # then adds the base value to the registered hosts
            host = host.replace("tcp://", "http://")
            host = str(host)
            self.hosts[base] = host

            # validates that the dashed version of the name is not the
            # same as the base one (at least one underscore) and if that's
            # not the case skips the current iteration
            if base == base_dash: continue

            # checks if the alias based registration is enabled and adds
            # the dashed version as an alias for such case or as an host
            # otherwise (static registration)
            if alias: self.alias[base_dash] = base
            else: self.hosts[base_dash] = host

    def _build_alias(self):
        linked = netius.conf_suffix("_ALIAS")
        for name, host in netius.legacy.iteritems(linked):
            base = name[:-6].lower()
            base_dash = base.replace("_", "-")
            self.alias[base] = host
            self.alias[base_dash] = host

    def _build_passwords(self):
        linked = netius.conf_suffix("_PASSWORD")
        for name, password in netius.legacy.iteritems(linked):
            base = name[:-9].lower()
            base_dash = base.replace("_", "-")
            simple_auth = netius.SimpleAuth(password = password)
            self.auth[base] = simple_auth
            self.auth[base_dash] = simple_auth

    def _build_redirect(self):
        linked = netius.conf_suffix("_REDIRECT")
        for name, host in netius.legacy.iteritems(linked):
            base = name[:-9].lower()
            base_dash = base.replace("_", "-")
            self.redirect[base] = host
            self.redirect[base_dash] = host

    def _build_error_urls(self):
        linked = netius.conf_suffix("_ERROR_URL")
        for name, error_url in netius.legacy.iteritems(linked):
            base = name[:-10].lower()
            base_dash = base.replace("_", "-")
            self.error_urls[base] = error_url
            self.error_urls[base_dash] = error_url

    def _build_redirect_ssl(self, alias = True):
        linked = netius.conf_suffix("_REDIRECT_SSL")
        for name, _force in netius.legacy.iteritems(linked):
            base = name[:-13].lower()
            base_dash = base.replace("_", "-")
            self.redirect[base] = (base, "https")
            self.redirect[base_dash] = (base_dash, "https")
            if not alias: continue
            for key, value in netius.legacy.iteritems(self.alias):
                is_match = value in (base, base_dash)
                if not is_match: continue
                self.redirect[key] = (key, "https")

    def _build_suffixes(self, alias = True, redirect = True):
        for host_suffix in self.host_suffixes:
            self.info("Registering %s host suffix" % host_suffix)
            for alias, value in netius.legacy.items(self.alias):
                fqn = alias + "." + str(host_suffix)
                self.alias[fqn] = value
            for name, value in netius.legacy.items(self.hosts):
                fqn = name + "." + str(host_suffix)
                if alias: self.alias[fqn] = name
                else: self.hosts[fqn] = value

    def _valid_url(self, value):
        """
        Verifies that the provided value is a valid URL
        value according to the default parsed.

        A valid URL should contain both a valid scheme and
        a not null and valid hostname.

        :type value: String
        :param value: The value that is going to be validated
        for URL compliance.
        :rtype: bool
        :return: If the provided value qualifies as a URL value.
        """

        value = str(value)
        result = netius.legacy.urlparse(value)
        if not result.scheme: return False
        if not result.hostname: return False
        return True

if __name__ == "__main__":
    server = DockerProxyServer()
    server.serve(env = True)
else:
    __path__ = []

#!/usr/bin/python # -*- coding: utf-8 -*- # Hive Netius System # Copyright (c) 2008-2020 Hive Solutions Lda. # # This file is part of Hive Netius System. # # Hive Netius System is free software: you can redistribute it and/or modify # it under the terms of the Apache License as published by the Apache # Foundation, either version 2.0 of the License, or (at your option) any # later version. # # Hive Netius System is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Apache License for more details. # # You should have received a copy of the Apache License along with # Hive Netius System. If not, see <http://www.apache.org/licenses/>. The author(s) of the module The version of the module The revision number of the module The last change date of the module The copyright for the module The license for the module Specialized reverse proxy server that handles many of the
     inner details of the reverse proxy server configuration
     server using environment variables information, making it
     easier for the automatic configuration and association of
     servers to the proxy server in a Docker environment.
 
     Although an optimal usage implies a Docker context the usage
     of the Docker proxy servers is not limited to such use cases. # retrieves the complete set of configuration values with the # regex suffix so that they are going to be used for the creation # of the regex rules (as expected) # retrieves the complete set of names from the linked items and then # in case the sort flag is set sorts their values (proper order) # iterates over the complete set of linked regex values splitting # the values around the proper token and adding them to the regex # tries to retrieve the complete set of configuration # values associated with the port suffix, this represents # the possible linked container addresses # iterates over the linked values, validating them and adding # them to the list of registered hosts # retrieves the name part of the configuration name # and converts it into lower cased value, note that # an extra dashed version is created, so that a proper # alias may be created for such naming # "builds" the name reference of the service and tries # to retrieve it from the configuration, it should exist # in case this port value represent a service # runs a series of validation on both the base and name # value to make sure that this value represents a valid # linked service/container # linked service/container (valid name reference found) # validates that the provided host is a valid URL value and # if that's not the case continues the loop (ignores) # replaces the prefix of the reference (assumes HTTP) and # then adds the base value to the registered hosts # validates that the dashed version of the name is not the # same as the base one (at least one underscore) and if that's # not the case skips the current iteration # checks if the alias based registration is enabled and adds # the dashed version as an alias for such case or as an host # otherwise (static registration) Verifies that the provided value is a valid URL
         value according to the default parsed.
 
         A valid URL should contain both a valid scheme and
         a not null and valid hostname.
 
         :type value: String
         :param value: The value that is going to be validated
         for URL compliance.
         :rtype: bool
         :return: If the provided value qualifies as a URL value.