Mini Shell
# -*- coding: utf-8 -*-
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
"""
This module contains classes implementing X-Ray Manager behaviour
for cPanel
"""
import json
import os
import subprocess
from collections import ChainMap
from pipes import quote
from typing import Optional
from clcommon.lib.whmapi_lib import WhmApiError, WhmApiRequest
from clcommon.utils import get_cl_version, is_litespeed_running
from xray.internal import phpinfo_utils
from ..internal.exceptions import XRayManagerError, XRayMissingDomain
from ..internal.types import DomainInfo
from ..internal.user_plugin_utils import (
user_mode_verification,
with_fpm_reload_restricted,
)
from .base import BaseManager
class CPanelManager(BaseManager):
"""
Class implementing an X-Ray manager behaviour for cPanel
"""
VERSIONS_cPanel = {
'ea-php54': '/opt/cpanel/ea-php54/root/etc/php.d',
'ea-php55': '/opt/cpanel/ea-php55/root/etc/php.d',
'ea-php56': '/opt/cpanel/ea-php56/root/etc/php.d',
'ea-php70': '/opt/cpanel/ea-php70/root/etc/php.d',
'ea-php71': '/opt/cpanel/ea-php71/root/etc/php.d',
'ea-php72': '/opt/cpanel/ea-php72/root/etc/php.d',
'ea-php73': '/opt/cpanel/ea-php73/root/etc/php.d',
'ea-php74': '/opt/cpanel/ea-php74/root/etc/php.d'
}
try:
if get_cl_version() != 'cl6':
VERSIONS_cPanel['ea-php80'] = '/opt/cpanel/ea-php80/root/etc/php.d'
VERSIONS_cPanel['ea-php81'] = '/opt/cpanel/ea-php81/root/etc/php.d'
VERSIONS_cPanel['ea-php82'] = '/opt/cpanel/ea-php82/root/etc/php.d'
except TypeError:
# primarily for the test run on the build system
pass
def supported_versions(self) -> ChainMap:
"""
Get supported PHP versions
:return: a chained map with basic supported versions
and cPanel supported versions
"""
return ChainMap(self.VERSIONS,
self.VERSIONS_cPanel)
def resolve_alias(self, domain_name: str) -> Optional[str]:
"""
Try to resolve domain_name if it is an alias
:param domain_name: original domain name
:return: resolved domain name alias
"""
try:
result = WhmApiRequest(
'domainuserdata'
).with_arguments(
domain=domain_name
).call()
except WhmApiError as e:
if 'system does not have a domain named' in str(e):
self.logger.warning('Domain does not exist on the server',
extra={'domain_name': domain_name})
raise XRayMissingDomain(domain_name) from e
# this None was here before this patch, just non-explicitly
return None
else:
return result['userdata']['servername']
def check_domain(self, name: str, domains_data: list,
original_name=None) -> DomainInfo:
"""
Try to find given name among known domains
:param name: name of domain to find
:param domains_data: list of known domains
:param original_name: original domain name (in case of alias resolving)
:return: a DomainInfo object
"""
domain = next((
item for item in domains_data
if item['vhost'] == name
), None)
if domain is None:
self.logger.warning('Domain does not exist on the server',
extra={'domain_name': name})
raise XRayMissingDomain(name)
if self.phpinfo_mode:
config = phpinfo_utils.get_php_configuration(
domain['account'],
domain=original_name if original_name else name)
return DomainInfo(
name=original_name if original_name else name,
panel_php_version=config.get_full_php_version('ea-php'),
php_ini_scan_dir=config.absolute_ini_scan_dir,
# indicates that there is no need to apply selector
# and try to resolve php version, the one given in
# php_version is final one
is_selector_applied=True,
user=domain['account'],
panel_fpm=config.is_php_fpm,
)
else:
return DomainInfo(
name=original_name if original_name else name,
panel_php_version=domain['version'],
user=domain['account'],
panel_fpm=domain[
'php_fpm'] if not is_litespeed_running() else False,
is_selector_applied=False
)
@user_mode_verification
@with_fpm_reload_restricted
def get_domain_info(self, domain_name: str) -> DomainInfo:
"""
Retrieve information about given domain from control panel environment:
PHP version, user of domain.
Try to resolve alias if domain was not found in API response
:param domain_name: name of domain
:return: a DomainInfo object
"""
result = WhmApiRequest(
'php_get_vhost_versions'
).call()
domain_php = result['versions']
try:
_info = self.check_domain(domain_name, domain_php)
except XRayManagerError:
alias = self.resolve_alias(domain_name)
_info = self.check_domain(alias, domain_php,
original_name=domain_name)
return _info
def domain_default_version_allows_selector(self, domain_php_version: str) -> bool:
"""
Check if given domain uses system default version.
And system default is not alt-php.
If yes, then it means that selector could be applied for given domain
:param domain_php_version: PHP version of domain
:return: True if yes, False otherwise
"""
result = WhmApiRequest(
'php_get_system_default_version'
).call()
default_php = result['version']
return 'alt-php' not in default_php and default_php == domain_php_version
def panel_specific_selector_enabled(self, domain_info: DomainInfo) -> bool:
"""
Check if selector is enabled specifically for panel
Required to be implemented by child classes
:param domain_info: a DomainInfo object
:return: True if yes, False otherwise
"""
domain_php_version = domain_info.panel_php_version
return (
# NOTE(vlebedev): CloudLinux PHP Selector in CPanel is considered to be enabled in two cases:
# 1. system defaul ea-php version is set as native for domain via MultiPHP Manager,
# 2. the same alt-php version is set for domain both via MultiPHP and via CL Selector.
#
# In the first case, the default ea-php version binaries are replaced with symlinks to /etc/cl.selector
# folder entries (which in turn contains symlinks to binaries of currently-chosen in CL Selector alt-php
# version).
#
# In the second case, not binaries but only PHP config folders are switched inside of cagefs:
# * for the alt-php version selected both via MultiPHP and CL Selector, /opt/alt/phpXX/link/conf is a
# symlink to /etc/cl.php.d/alt-php74 (/var/cagefs/<uid>/<user>/etc/cl.php.d/alt-phpXX outside of cagefs),
# * other alt-php versions are still symlinked to /opt/alt/phpXX/etc/php.d (unless overriden by
# /etc/cl.selector/symlinks.rules)
self.domain_default_version_allows_selector(domain_php_version)
or domain_php_version == domain_info.selector_php_version
) and not domain_info.panel_fpm
def fpm_service_name(self, dom_info: DomainInfo) -> str:
"""
Get cPanel FPM service name
:param dom_info: a DomainInfo object
:return: FPM service name
"""
return f'{dom_info.panel_php_version}-php-fpm'
Zerion Mini Shell 1.0