Nekmo/simple-monitor-alert

View on GitHub
setup.py

Summary

Maintainability
F
3 days
Test Coverage
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import distutils
import filecmp
import getpass
import shutil
import subprocess

import pwd

import grp
from distutils.version import LooseVersion

from setuptools import setup, find_packages, Command
import pip
from distutils.util import convert_path
from fnmatch import fnmatchcase
import os
import sys
import uuid
from simple_monitor_alert.utils.files import makedirs

if hasattr(pip, '__version__') and LooseVersion(pip.__version__) >= LooseVersion('6.0.0'):
    from pip.req import parse_requirements
else:
    class FakeReq(object):
        link = None

        def __init__(self, req):
            self.req = req

    def parse_requirements(reqs_path, *args, **kwargs):
        return [FakeReq(line.strip()) for line in open(reqs_path).readlines()]


def first_path_exist(paths):
    for test_path in paths:
        if os.path.exists(test_path):
            return test_path

###############################
#  Configuración del paquete  #
###############################

# Instrucciones:
# 1. Rellene la información de esta sección.
# 2. Incluya un archivo requirements.txt con las dependencias.
# 3. Cambie el archivo LICENSE.txt por el de su licencia.
# 4. Añada un archivo README, README.rst o README.md, el cual se trata de la la descripción extendida.

ENABLED_MONITORS_DIR = '/etc/simple-monitor-alert/monitors-enabled'
AVAILABLE_MONITORS_DIR = '/etc/simple-monitor-alert/monitors-available'
MONITORS_DIR = '/usr/lib/simple-monitor-alert/monitors'
ALERTS_DIR = '/usr/lib/simple-monitor-alert/alerts'
AVAILABLE_ALERTS_DIR = '/etc/simple-monitor-alert/alerts'
SMA_TEMPLATE_FILENAME = 'sma-template.ini'
SMA_FILE = '/etc/simple-monitor-alert/sma.ini'
DEFAULT_ENABLEDS_MONITORS = ['hdds.sh', 'system.sh']
USERNAME = 'sma'
VAR_DIRECTORY = '/var/lib/simple-monitor-alert'

SERVICES = [
    (
        'services/sma.service',
        '{}/sma.service'.format(first_path_exist(['/usr/lib/systemd/system', '/lib/systemd/system']))
    ),
    (
        'services/sma.sh',
        '/etc/init.d/sma.sh'
    )
]

#  Información del autor
from setuptools.command.install import install

AUTHOR = 'Nekmo'
EMAIL = 'contacto@nekmo.com'

# Información del paquete
PACKAGE_NAME = 'simple-monitor-alert'
PACKAGE_DOWNLOAD_URL = 'https://github.com/Nekmo/simple-monitor-alert/archive/master.zip'  # .tar.gz
URL = 'https://github.com/Nekmo/simple-monitor-alert'  # Project url
STATUS_LEVEL = 1  # 1:Planning 2:Pre-Alpha 3:Alpha 4:Beta 5:Production/Stable 6:Mature 7:Inactive
KEYWORDS = ['linux', 'unix', 'monitor', 'alert', 'simple-monitor-alert', 'notifications', 'email', 'telegram']
# https://github.com/github/choosealicense.com/tree/gh-pages/_licenses
LICENSE = 'MIT'
CLASSIFIERS = [
    # Common licenses
    'License :: OSI Approved :: MIT License',
    'Topic :: System :: Networking :: Monitoring',
    'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
    'Topic :: System :: Monitoring',
    'Operating System :: POSIX :: Linux',
    'Operating System :: POSIX :: BSD',
    'Operating System :: POSIX',
    # 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
    # 'License :: OSI Approved :: BSD License',
]  # https://pypi.python.org/pypi?%3Aaction=list_classifiers
NATURAL_LANGUAGE = 'English'  # English...
DESCRIPTION = """\
A simple monitor with alerts for Unix/Linux under the KISS philosophy. Keep It Simple, Stupid!
"""

# Requerido para la correcta instalación del paquete
PLATFORMS = [
    # 'universal',
    'linux',
    # 'macosx',
    # 'solaris',
    # 'irix',
    # 'win'
    # 'bsd'
    # 'ios'
    # 'android'
]
ROOT_INCLUDE = ['requirements.txt', 'VERSION', 'LICENSE.txt']
PYTHON_VERSIONS = ['2.7', '3.3-3.5']  # or ranges: 3.1-3.5, 2.6-3.4...
INSTALL_REQUIRES = []  # Necesario si no hay un requirements.txt

######## FIN DE LA CONFIGURACIÓN DEL PAQUTE ########

__author__ = AUTHOR
__dir__ = os.path.abspath(os.path.dirname(__file__))

# paths
readme_path = os.path.join(__dir__, 'README')
for target in ['README.rst', 'README.md']:
    if not os.path.exists(readme_path):
        readme_path = os.path.join(__dir__, target)

version_path = os.path.join(__dir__, 'VERSION')
requirements_path = os.path.join(__dir__, 'requirements.txt')
scripts_path = os.path.join(__dir__, 'scripts')


def get_url(ir):
    if hasattr(ir, 'url'): return ir.url
    if ir.link is None: return
    return ir.link.url


##############################################################################
# find_package_data is an Ian Bicking creation.

# Provided as an attribute, so you can append to these instead
# of replicating them:
standard_exclude = ('*.py', '*.pyc', '*~', '.*', '*.bak', '*.swp*')
standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
                                './dist', 'EGG-INFO', '*.egg-info')


def find_package_data(where='.', package='',
                      exclude=standard_exclude,
                      exclude_directories=standard_exclude_directories,
                      only_in_packages=True,
                      show_ignored=False):
    """
    Return a dictionary suitable for use in ``package_data``
    in a distutils ``setup.py`` file.

    The dictionary looks like::

        {'package': [files]}

    Where ``files`` is a list of all the files in that package that
    don't match anything in ``exclude``.

    If ``only_in_packages`` is true, then top-level directories that
    are not packages won't be included (but directories under packages
    will).

    Directories matching any pattern in ``exclude_directories`` will
    be ignored; by default directories with leading ``.``, ``CVS``,
    and ``_darcs`` will be ignored.

    If ``show_ignored`` is true, then all the files that aren't
    included in package data are shown on stderr (for debugging
    purposes).

    Note patterns use wildcards, or can be exact paths (including
    leading ``./``), and all searching is case-insensitive.

    This function is by Ian Bicking.
    """

    out = {}
    stack = [(convert_path(where), '', package, only_in_packages)]
    while stack:
        where, prefix, package, only_in_packages = stack.pop(0)
        for name in os.listdir(where):
            fn = os.path.join(where, name)
            if os.path.isdir(fn):
                bad_name = False
                for pattern in exclude_directories:
                    if (fnmatchcase(name, pattern)
                        or fn.lower() == pattern.lower()):
                        bad_name = True
                        if show_ignored:
                            sys.stderr.write(
                                "Directory %s ignored by pattern %s\n"
                                % (fn, pattern))
                        break
                if bad_name:
                    continue
                if os.path.isfile(os.path.join(fn, '__init__.py')):
                    if not package:
                        new_package = name
                    else:
                        new_package = package + '.' + name
                    stack.append((fn, '', new_package, False))
                else:
                    stack.append(
                        (fn, prefix + name + '/', package, only_in_packages)
                    )
            elif package or not only_in_packages:
                # is a file
                bad_name = False
                for pattern in exclude:
                    if (fnmatchcase(name, pattern)
                        or fn.lower() == pattern.lower()):
                        bad_name = True
                        if show_ignored:
                            sys.stderr.write(
                                "File %s ignored by pattern %s\n"
                                % (fn, pattern))
                        break
                if bad_name:
                    continue
                out.setdefault(package, []).append(prefix + name)
    return out


##############################################################################

# Lista de dependencias a instalar
if os.path.exists(requirements_path):
    requirements = list(parse_requirements(requirements_path, session=uuid.uuid1()))
    install_requires = [str(ir.req) for ir in requirements]
    dependency_links = [get_url(ir) for ir in requirements if get_url(ir)]
else:
    install_requires = INSTALL_REQUIRES
    dependency_links = []

# Todos los módulos y submódulos a instalar (module, module.submodule, module.submodule2...)
packages = find_packages(__dir__)
# Prevent include symbolic links
for package in tuple(packages):
    path = os.path.join(__dir__, package.replace('.', '/'))
    if not os.path.exists(path):
        continue
    if not os.path.islink(path):
        continue
    packages.remove(package)

# Otros archivos que no son Python y que son requeridos
package_data = {'': ROOT_INCLUDE}

# Obtener la lista de módulos que se instalarán
modules = list(filter(lambda x: '.' not in x, packages))

for module in modules:
    package_data.update(find_package_data(
        module,
        package=module,
        only_in_packages=False,
    ))

# Descripción larga si existe un archivo README
try:
    long_description = open(readme_path, 'rt').read()
except IOError:
    long_description = ''

# Tomar por defecto la versión de un archivo VERSION. Si no, del módulo
if os.path.exists(version_path):
    package_version = open(version_path).read().replace('\n', '')
else:
    package_version = __import__(modules[0]).__version__

# Si hay un directorio scripts, tomar todos sus archivos
if os.path.exists(scripts_path):
    scripts_dir_name = scripts_path.replace(__dir__, '', 1)
    scripts_dir_name = scripts_dir_name[1:] if scripts_dir_name.startswith(os.sep) else scripts_dir_name
    scripts = [os.path.join(scripts_dir_name, file) for file in os.listdir(scripts_path)]
else:
    scripts = []

# Eliminar archivos de ROOT_INCLUDE que no existen
for d in tuple(ROOT_INCLUDE):
    if not os.path.exists(os.path.join(__dir__, d)):
        ROOT_INCLUDE.remove(d)

# Nombre del estado de desarrollo
status_name = ['Planning', 'Pre-Alpha', 'Alpha', 'Beta',
               'Production/Stable', 'Mature', 'Inactive'][STATUS_LEVEL - 1]

# Añadir en los classifiers la plataforma
platforms_classifiers = {'linux': ('POSIX', 'Linux'), 'win': ('Microsoft', 'Windows'),
                         'solaris': ('POSIX', 'SunOS/Solaris'), 'aix': ('POSIX', 'Linux'), 'unix': ('Unix',),
                         'bsd': ('POSIX', 'BSD')}
for key, parts in platforms_classifiers.items():
    if not key in PLATFORMS:
        continue
    CLASSIFIERS.append('Operating System :: {0}'.format(' :: '.join(parts)))


# Añadir la versión de Python a los Classifiers
def frange(x, y, jump):
    while x < y:
        yield x
        x += jump


python_versions = []
for version in PYTHON_VERSIONS:
    if '-' in version:
        version = version.split('-')
        if len(version) != 2:
            raise ValueError('Invalid Python version range: {0}'.format('-'.join(version)))
        version = list(map(float, version))
        version[1] += 0.1  # Para que frange incluya la última versión
        python_versions.extend(frange(version[0], version[1], 0.1))
    elif isinstance(version, int) or version.isdigit():
        python_versions.append(str(version))
    else:
        python_versions.append(float(version))
python_versions = map(lambda x: x if isinstance(x, str) else '%.1f' % x, python_versions)
# Eliminar versiones 0-2.3 y 2.8-2.9
remove_python_versions = map(str, list(frange(0, 2.3, 0.1)) + list(frange(2.8, 3.0, 0.1)))
python_versions = list(filter(lambda x: x not in remove_python_versions, python_versions))
for version in range(2, 4):
    if not len(list(filter(lambda x: int(float(x)) != version, python_versions))):
        # Sólo se encuentran versiones para la versión <version>
        python_versions.append('%i :: Only' % version)
        break
CLASSIFIERS.extend(['Programming Language :: Python :: %s' % version for version in python_versions])
CLASSIFIERS.extend([
    'Natural Language :: {0}'.format(NATURAL_LANGUAGE),
    'Development Status :: {0} - {1}'.format(STATUS_LEVEL, status_name),
])


def create_backup(file):
    if not os.path.lexists(file):
        return
    backup_file = file + '.bak'
    i = 0
    while os.path.lexists(backup_file):
        new_backup_file = '{}{}'.format(backup_file, i)
        if not os.path.lexists(new_backup_file):
            backup_file = new_backup_file
            break
        i += 1
    shutil.move(file, backup_file)


class SystemInstallCommand(install):
    """Custom install setup to help run shell commands (outside shell) before installation"""

    def run(self):
        print('Starting Python module installation.')
        self.do_egg_install()
        print('-' * 80)
        if getpass.getuser() != 'root':
            print('WARNING: Simple-Monitor-Alert installed as "{}". Install as root to create the system files!')
            return
        print('Installing things that are not Python (system files).')
        for directory in [ENABLED_MONITORS_DIR, os.path.dirname(AVAILABLE_MONITORS_DIR)]:
            print('Creating directory {}'.format(directory))
            makedirs(directory, exist_ok=True)
        dir_path = os.path.dirname(os.path.realpath(__file__))
        for src_name, dest in [('monitors', MONITORS_DIR), ('alerts', ALERTS_DIR)]:
            print('Copying directory "{}" to "{}"'.format(os.path.join(dir_path, src_name), dest))
            distutils.dir_util.copy_tree(os.path.join(dir_path, src_name), dest)
        for from_, to in [(MONITORS_DIR, AVAILABLE_MONITORS_DIR), (ALERTS_DIR, AVAILABLE_ALERTS_DIR)]:
            if not os.path.exists(to):
                print('Creating symbolic link "{}" -> "{}"'.format(from_, to))
                os.symlink(from_, to)
        if not os.listdir(ENABLED_MONITORS_DIR):
            print('Enabling defaults monitors: {}'.format(', '.join(DEFAULT_ENABLEDS_MONITORS)))
            for enabled_monitor in DEFAULT_ENABLEDS_MONITORS:
                os.symlink('../{}/{}'.format(os.path.split(AVAILABLE_MONITORS_DIR)[1], enabled_monitor),
                           os.path.join(ENABLED_MONITORS_DIR, enabled_monitor))
        print('Creating user {}'.format(USERNAME))
        p = subprocess.Popen(['useradd', USERNAME])
        if sys.version_info >= (3,3):
            p.wait(2)
        else:
            p.wait()
        if p.returncode not in [0, 9]:
            raise Exception('It has failed to create the user {}. Returncode: {}'.format(USERNAME, p.returncode))
        print('Creating variable data directory: {}'.format(VAR_DIRECTORY))
        makedirs(VAR_DIRECTORY, 0o700, True)
        uid = pwd.getpwnam(USERNAME).pw_uid
        gid = grp.getgrnam("root").gr_gid
        os.chown(VAR_DIRECTORY, uid, gid)
        print('Copying services')
        for src, dest in SERVICES:
            if not os.path.lexists(os.path.dirname(dest)):
                continue
            if os.path.exists(dest) and filecmp.cmp(os.path.join(dir_path, src), dest):
                continue
            create_backup(dest)
            shutil.copy(os.path.join(dir_path, src), dest)
        print('Copying sma template file')
        if not os.path.lexists(SMA_FILE):
            shutil.copy(os.path.join(dir_path, SMA_TEMPLATE_FILENAME), SMA_FILE)


class FakeBdistWheel(Command):
    description = 'Faked Wheel'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        print('Sorry, but wheel is not supported for this package!')


setup(
    cmdclass={'install': SystemInstallCommand, 'bdist_wheel': FakeBdistWheel},

    name=PACKAGE_NAME,
    version=package_version,

    description=DESCRIPTION,
    long_description=long_description,

    author=AUTHOR,
    author_email=EMAIL,

    url=URL,

    license=LICENSE,
    classifiers=CLASSIFIERS,

    platforms=PLATFORMS,

    provides=modules,
    install_requires=install_requires,
    dependency_links=dependency_links,

    packages=packages,
    include_package_data=True,
    # Scan the input for package information
    # to grab any data files (text, images, etc.)
    # associated with sub-packages.
    package_data=package_data,

    download_url=PACKAGE_DOWNLOAD_URL,
    keywords=KEYWORDS,
    scripts=scripts,

    # entry_points={},

    zip_safe=False,
)