Mini Shell
# -*- 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