Mini Shell

Direktori : /proc/thread-self/root/opt/alt/python37/lib/python3.7/site-packages/ssa/internal/
Upload File :
Current File : //proc/thread-self/root/opt/alt/python37/lib/python3.7/site-packages/ssa/internal/utils.py

"""
This module contains helpful utility functions for SSA Agent
"""
import datetime
import logging
import os
import shelve
import xml.etree.ElementTree as ET
from collections import namedtuple
from contextlib import contextmanager
from datetime import date, timedelta
from socket import socket, fromfd, AF_UNIX, SOCK_STREAM, AF_INET, AF_INET6, \
    SOCK_DGRAM
from urllib.parse import urlparse

import sentry_sdk
from sentry_sdk.integrations.atexit import AtexitIntegration
from sentry_sdk.integrations.logging import LoggingIntegration

from .constants import sentry_dsn
from ..internal.constants import storage_file

logger = logging.getLogger('utils')
URL = namedtuple('URL', ['domain_name', 'uri_path'])


# --------- FUNCTIONS ---------

def url_split(url: str) -> URL:
    """
    Split URL into domain_name and uripath including query string
    :param url: URL of format protocol://domain/path;parameters?query#fragment
    :return: namedtuple URL(domain_name, uripath)
    """
    fragments = urlparse(url)
    qs = f'?{fragments.query}' if fragments.query else ''
    uri = f'{fragments.path}{qs}' if fragments.path else '/'
    # logger.info('Parsed %s into %s:%s', url, fragments.netloc, uri)
    return URL(fragments.netloc.replace('www.', ''), uri)


def load_collected_stats() -> dict:
    """
    Try to load stats from file.
    In case of fail instantiate empty struct
    """
    logger.info('Loading data from storage...')
    try:
        with shelve.open(storage_file) as db:
            return {item: db[item] for item in db.keys()}
    except OSError as e:
        logger.error(
            'Failed to load data from storage: %s', str(e),
            extra={'err': str(e)})
        return dict()


def sentry_init() -> None:
    """
    Initialize Sentry client
    shutdown_timeout=0 disables Atexit integration as stated in docs:
    'it’s easier to disable it by setting the shutdown_timeout to 0'
    https://docs.sentry.io/platforms/python/default-integrations/#atexit
    On the other hand, docs say, that
    'Setting this value too low will most likely cause problems
    for sending events from command line applications'
    https://docs.sentry.io/error-reporting/configuration/?platform=python#shutdown-timeout
    """

    def add_info(event: dict, hint: dict) -> dict:
        """
        Add extra data into sentry event
        :param event: original event
        :param hint: additional data caught
        :return: updated event
        """
        event['extra'].update({'ssa.version': '0.1-9.el7'})
        return event

    def try_get_ip(address_family, private_ip):
        """
        address_family - we can choose constants represent the address
                           (and protocol) families
                           (AF_INET for ipv4 and AF_INET6 for ipv6)
        private_ip - specify some private ip address. For instance:
                     ipv4 -> 10.255.255.255 or ipv6 -> fc00::
        """
        with socket(address_family, SOCK_DGRAM) as s:
            try:
                s.connect((private_ip, 1))
                IP = s.getsockname()[0]
            except Exception:
                IP = None
        return IP

    def get_ip():
        """
        We are trying to get an IPv4 or IPv6 address.
        In case of failure we'll return 127.0.0.1
        """
        ipversions = (AF_INET, '10.255.255.255'), (AF_INET6, 'fc00::')
        for addr_fam, priv_ip in ipversions:
            ip = try_get_ip(addr_fam, priv_ip)
            if ip:
                return ip
        return '127.0.0.1'

    def nope(pending, timeout) -> None: pass

    sentry_logging = LoggingIntegration(level=logging.INFO,
                                        event_level=logging.WARNING)
    silent_atexit = AtexitIntegration(callback=nope)
    sentry_sdk.init(dsn=sentry_dsn, before_send=add_info,
                    release="alt-php-ssa@0.1-9.el7",
                    integrations=[sentry_logging, silent_atexit])
    with sentry_sdk.configure_scope() as scope:
        scope.user = {"ip_address": get_ip()}


def set_logging_into_file(fname: str, as_error: bool = False) -> str:
    """
    Try to configure logging into given fname
    If as_error True, log the exception as ERROR, otherwise -- as INFO
    """
    try:
        logging.basicConfig(filename=fname, level=logging.INFO,
                            format='%(asctime)s %(message)s',
                            datefmt='%m/%d/%Y %I:%M:%S %p')
        try:
            os.chmod(fname, 0o666)
        except PermissionError:
            pass
        return fname
    except OSError as e:
        logger.log(logging.ERROR if as_error else logging.INFO,
                   'No logging configuration applied: %s',
                   str(e))


def configure_logging(logname: str) -> str:
    """
    Configure logging
    :param logname: path to log
    :return: logpath
    """
    sentry_init()
    if set_logging_into_file(logname) is None:
        try:
            os.makedirs(os.path.dirname(logname))
        except Exception as e:
            logger.warning('Failed to create logdir %s', str(e))
            return ''
        logname = set_logging_into_file(logname, as_error=True)
    return logname


def create_socket(sock_location: str) -> 'socket object':
    """
    Create world-writable socket in given sock_location
    or reuse existing one
    :param sock_location: socket address
    :return: socket object
    """
    LISTEN_FDS = int(os.environ.get("LISTEN_FDS", 0))
    if LISTEN_FDS == 0:
        with umask_0():
            sockobj = socket(AF_UNIX)
            sockobj.bind(sock_location)
            sockobj.listen()
    else:
        sockobj = fromfd(3, AF_UNIX, SOCK_STREAM)
        sockobj.listen()
    return sockobj


def previous_day_date() -> str:
    """
    Returns date of previous day in a format "day.month.year"
    """
    yesterday = date.today() - timedelta(days=1)
    return yesterday.strftime('%d.%m.%Y')


def format_date(datestr: str, formatstr='%d.%m.%Y') -> str:
    """
    Convert date to format YYYY-mm-dd
    """
    _date = datetime.datetime.strptime(datestr, formatstr)
    return _date.strftime("%Y-%m-%d")


def read_sys_id() -> str:
    """
    Obtain system ID from /etc/sysconfig/rhn/systemid
    :return: system ID without ID- prefix
    """
    try:
        tree = ET.parse('/etc/sysconfig/rhn/systemid')
        root = tree.getroot()
        whole_id = root.find(".//member[name='system_id']/value/string").text
        with sentry_sdk.configure_scope() as scope:
            scope.set_tag("system_id", whole_id)
        return whole_id.lstrip('ID-')
    except (OSError, ET.ParseError) as e:
        logger.warning('Failed to retrieve system_id: %s', str(e))


def duration_cast(duration: int) -> float:
    """
    Cast duration from microseconds to seconds leaving 2 digits after point
    """
    return float(format(duration/1000000, '0.2f'))


# --------- CONTEXT MANAGERS ---------


@contextmanager
def umask_0() -> None:
    """
    Context manager for dropping umask
    """
    prev = os.umask(0)
    yield
    os.umask(prev)


# --------- DECORATORS ---------

def singleton(some_cls):
    class __Singleton:
        """
        A singleton wrapper class. Its instances would be created
        for each decorated class.
        """

        def __init__(self, _cls):
            self._wrapped = _cls
            self._instance = None

        def __call__(self, *args, **kwargs):
            """Returns a single instance of decorated class"""
            if self._instance is None:
                self._instance = self._wrapped(*args, **kwargs)
            return self._instance

    return __Singleton(some_cls)

Zerion Mini Shell 1.0