%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/thread-self/root/opt/cloudlinux/venv/lib/python3.11/site-packages/clcommon/lib/
Upload File :
Create Path :
Current File : //proc/thread-self/root/opt/cloudlinux/venv/lib/python3.11/site-packages/clcommon/lib/whmapi_lib.py

# -*- coding: utf-8 -*-

# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
#

"""
Everything that is related to whmapi calls
"""

import json
from clcommon import FormattedException
from clcommon.utils import run_command
from urllib.parse import urlencode

__all__ = ('WhmApiRequest', 'WhmApiError')


class WhmApiError(FormattedException):
    """
    An error that is raised in case of an error
    in communication with whmapi.
    """

    def __init__(self, message, **context):
        FormattedException.__init__(self, {
            'message': message,
            'context': context
        })


class WhmLicenseError(WhmApiError):
    """A license-related error raised by whmapi."""
    pass


class WhmNoPhpBinariesError(WhmApiError):
    """
    An error when there are no installed php binaries
    """
    pass


class WhmApiRequest:
    """
    Wrapper over cpanel's whm command-line api tool
    that allows us to easily build complex requests (filter, sorting, etc)

    See details in the official cpanel docs (link below)
    https://documentation.cpanel.net/display/DD/Guide+to+WHM+API+1
    """
    WHMAPI = '/usr/sbin/whmapi1'

    API_RESULT_OK = 1

    def __init__(self, command):
        self._command = command
        self._filters = {}
        self._args = {}
        self._extra_args = ['--output', 'json']

    def _run_whmapi(self, command):
        exitcode, output, _ = run_command(
            command, return_full_output=True)
        if exitcode != 0:
            raise WhmApiError(
                'whmapi exited with code %(code)i',
                code=exitcode
            )

        try:
            response = json.loads(output)
            # TODO: PTCCLIB-196
            # Starting with cPanel v86.0 whmapi1 returns empty reason
            # if we try to revome nonexistent package.
            # It's temporary solution until cPanel provides another one.
            if ('metadata' in response)\
                    and ('reason' in response['metadata'])\
                    and (response['metadata']['reason'] is None):
                response['metadata']['reason'] = ''
        except (TypeError, ValueError) as e:
            raise WhmApiError(
                'whmapi returned invalid response that '
                'cannot be parsed with json, output: %(output)s',
                output=output
            ) from e
        self._validate(response)
        return response

    @classmethod
    def _validate(cls, response):
        """
        Check response metadata
        """
        if cls._is_license_error(response):
            raise WhmLicenseError(
                'whmapi license error: %(response)s', response=response['statusmsg']
            )
        if cls._is_no_php_binaries_error(response):
            raise WhmNoPhpBinariesError(
                'Php binaries error: %(response)s', response=response['metadata']['reason']
            )
        try:
            result, reason = \
                response['metadata']['result'], response['metadata']['reason']
        except KeyError as e:
            raise WhmApiError(
                'whmapi metadata section is broken, output: %(response)s',
                response=response
            ) from e

        # in 'ideal' world this should never happen as we check whmapi exitcode
        if result != WhmApiRequest.API_RESULT_OK:
            raise WhmApiError(
                'whmapi failed to execute request, reason: %(reason)s',
                reason=reason
            )

    @staticmethod
    def _is_license_error(response):
        """
        Distinguish license-related WHM API errors from others.
        License errors are on the client's side, and should not be logged to sentry.
        An error is considered license-related if the API returns status 0
        and the error message contains the word 'license'
        """
        return ('statusmsg' in response and
                response['status'] == 0 and
                'license' in response['statusmsg'].lower())

    @staticmethod
    def _is_no_php_binaries_error(response):
        """
        No binaries error can be detected by  checking special message
        '“PHP” is not installed on the system' whmapi output
        """
        return ('metadata' in response and
                'reason' in response['metadata'] and
                '“PHP” is not installed on the system' in response['metadata']['reason'])

    def with_arguments(self, **kwargs):
        """
        Add some extra arguments to subprocess call
        Useful for methods like createacct, removeacct
        :param kwargs: arguments that will be added to cmd
        :rtype: WhmApiRequest
        """
        self._args.update(kwargs)
        return self

    # TODO: enable in the future and add some unittests
    # def filter(self, **kwargs):
    #     """
    #     Implements output filtering, see the following url for details
    #     https://documentation.cpanel.net/display/DD/WHM+API+1+-+Filter+Output
    #     :param kwargs: dict
    #     """
    #     self._filters.update(kwargs)
    #     return self

    def call(self):
        """
        Run subprocess, run output validation and
        return json-loaded response
        :return:
        """
        cmd = [
            self.WHMAPI,
            self._command
        ]
        for k, v in list(self._args.items()):
            # https://documentation.cpanel.net/display/DD/Guide+to+WHM+API+1
            if isinstance(v, bool):
                # the term "boolean" in our documentation refers
                # to parameters that accept values of 1 or 0.
                # cPanel & WHM's APIs do not support the literal
                # values of true and false.
                v = int(v)
            argument = urlencode({k: v})
            cmd.append(argument)

        # TODO: enable in the future and add some unittests
        # for key, value in self._filters.items():
        #     cmd.extend('api.filter.{}={}'.format(key, value))

        cmd.extend(self._extra_args)

        result = self._run_whmapi(cmd)

        if 'data' in result:
            # for getting method
            return result['data']
        elif 'output' in result:
            # for setting method
            return result['output']
        else:
            return result

Zerion Mini Shell 1.0