Mini Shell
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
import configparser
import io
import os
import json
import fcntl
import struct
import time
from typing import Optional, Dict, AnyStr, Union
from jwt import decode, exceptions
from clcommon.lib.consts import DEFAULT_JWT_ES_TOKEN_PATH
from clcommon.lib.whmapi_lib import WhmApiRequest, WhmApiError
from clcommon.clcaptain import mkdir as mkdir_p
from clcommon.utils import process_is_running
from clconfig.cagefs_statistics_config import check_cagefs_initialized
from cldetectlib import CL_CONFIG_FILE
from secureio import write_file_via_tempfile
def dummy_none_function(*a, **kw):
return None
try:
from clselect.clselectctl import interpreter_versions_short_summary
from clselector.clpassenger_detectlib import is_clpassenger_active
except ImportError:
interpreter_versions_short_summary = dummy_none_function
is_clpassenger_active = dummy_none_function
_CL_STATISTICS_SECTION = "license_check"
_CL_STATISTICS_COLLECT_STATE_OPTION = "cl_statistics_enabled"
_CL_STATISTICS_COLLECT_RPM_STATE_OPTION = "cl_statistics_rpm_enabled"
_CL_STATISTICS_DIR = '/var/lve'
_CL_STATISTICS_SEND_STATUS_FILE = os.path.join(_CL_STATISTICS_DIR, 'summary_status.json')
_CL_STATISTICS_LOCK_PATH = '/var/run/cloudlinux_summary.send.lock'
_CL_STATISTICS_LOCK_FILE = None
CL_PLUS_SENDER_FILE_PATH = '/usr/share/cloudlinux/cl_plus/clplus_sender.py'
ALT_PYTHON_VIRTUALENV_BIN = '/opt/cloudlinux/venv/bin/virtualenv'
class SummaryStatus:
"""
Status of both, collecting and sending statistics
If process still collects statistics -> IN_PROGRESS
If statistics collected and sent correctly -> SUCCESS
If any error during collecting or sending -> FAILED
"""
SUCCESS = 'success'
IN_PROGRESS = 'in_progress'
FAILED = 'failed'
def is_virtualenv_installed():
"""
Checks is virtualenv installed
:return: True/False - installed or not
"""
return os.path.exists(ALT_PYTHON_VIRTUALENV_BIN)
def is_locked(lock_file):
"""
Check if file is locked by another process without acquiring lock.
IMPORTANT! This function should NOT be used to check lock acquired by the
same process that executes the is_locked() function. For example, when
process executes fcntl.lockf(LOCK_FILE), and then the same process executes
is_locked(LOCK_FILE), the is_locked(LOCK_FILE) call returns False.
Use is_locked() function to check lock acquired by another process only.
:param lock_file: file to check lock on
:type lock_file: file object or descriptor
"""
lock_data = struct.pack("hhllhh", fcntl.F_WRLCK, 0, 0, 0, 0, 0)
try:
lock_query = fcntl.fcntl(lock_file, fcntl.F_GETLK, lock_data)
lock_status = struct.unpack("hhllhh", lock_query)[0]
except (OSError, IOError):
# should never happen
return False
return lock_status != fcntl.F_UNLCK
def is_sending_process_running(acquire_lock=False):
"""
Check if processes collecting stats are running already (with --send option in command line)
:param acquire_lock: True = acquire lock when possible
:type acquire_lock: bool
:return bool: True = Processes are running
"""
global _CL_STATISTICS_LOCK_FILE
_CL_STATISTICS_LOCK_FILE = open( # pylint: disable=consider-using-with
_CL_STATISTICS_LOCK_PATH, 'w', encoding='utf-8'
)
if not acquire_lock:
return is_locked(_CL_STATISTICS_LOCK_FILE)
try:
fcntl.lockf(_CL_STATISTICS_LOCK_FILE, fcntl.LOCK_EX | fcntl.LOCK_NB)
except (OSError, IOError):
return True
return False
def _get_config():
"""
Retrieves ConfigParser object for /etc/sysconfig/cloudlinux file
:return:
"""
config = configparser.ConfigParser(interpolation=None,
strict=True)
config.optionxform = str # make config case sensitive
config.read(CL_CONFIG_FILE)
return config
def _write_config(config):
"""
write config to /etc/sysconfig/cloudlinux file
:param config: configParser object to write
return boolean: True|False
"""
try:
string_fp = io.StringIO()
config.write(string_fp)
content = string_fp.getvalue()
write_file_via_tempfile(content, CL_CONFIG_FILE, 0o644, prefix='cloudlinux_')
except (OSError, IOError):
return False
return True
def _get_config_value(parameter: str, default: bool = True) -> bool:
"""
Retrieves parameter's value from /etc/sysconfig/cloudlinux file, _CL_STATISTICS_SECTION section
"""
config = _get_config()
res = default
try:
res = config.getboolean(_CL_STATISTICS_SECTION, parameter)
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
# Treat absent/missing value as default
pass
return res
def _set_config_value(parameter: str, value: bool) -> None:
"""
Sets parameter's value to /etc/sysconfig/cloudlinux file, _CL_STATISTICS_SECTION section
"""
config = _get_config()
config.set(_CL_STATISTICS_SECTION, parameter, str(int(value)))
_write_config(config)
def is_statistic_enabled() -> bool:
"""
Retrieves statistic collection status from /etc/sysconfig/cloudlinux file
:return: True/False - enabled/disabled
"""
return _get_config_value(_CL_STATISTICS_COLLECT_STATE_OPTION)
def is_statistic_rpm_enabled() -> bool:
"""
Retrieves rpm statistic collection status from /etc/sysconfig/cloudlinux file
:return: True/False - enabled/disabled
"""
return _get_config_value(_CL_STATISTICS_COLLECT_RPM_STATE_OPTION)
def set_statistic_collection_enabled(is_enabled: bool) -> None:
"""
Set statistic collection status to /etc/sysconfig/cloudlinux file
:param is_enabled: True/False - enabled/disabled
"""
_set_config_value(_CL_STATISTICS_COLLECT_STATE_OPTION, is_enabled)
def set_statistic_rpm_collection_enabled(is_enabled: bool) -> None:
"""
Set rpm statistic collection status to /etc/sysconfig/cloudlinux file
:param is_enabled: True/False - enabled/disabled
"""
_set_config_value(_CL_STATISTICS_COLLECT_RPM_STATE_OPTION, is_enabled)
def write_statistics_send_status_to_file(status_dict):
"""
Writes statistics send status to file /var/lve/summary_status.json
:param status_dict: status dictionary for write to file
:return:
"""
try:
if not os.path.exists(_CL_STATISTICS_DIR):
mkdir_p(_CL_STATISTICS_DIR)
content = json.dumps(status_dict)
# Write to file readable only for root
write_file_via_tempfile(content, _CL_STATISTICS_SEND_STATUS_FILE, 0o600, prefix='cloudlinux_')
except (OSError, IOError):
pass
def get_statistics_send_status_from_file():
"""
Retrieves statistics send status from file /var/lve/summary_status.json
:return: Dictionary with last send status. None if any error
"""
status_dict = None
try:
with open(_CL_STATISTICS_SEND_STATUS_FILE, encoding='utf-8') as f:
s_content = f.read()
status_dict = json.loads(s_content)
if status_dict['result'] == SummaryStatus.IN_PROGRESS \
and not is_sending_process_running():
# something went wrong during collection
status_dict['result'] = SummaryStatus.FAILED
status_dict['reason'] = 'Collecting statistics was failed. Error ' \
'report has been sent to developers and will be fixed soon'
except (OSError, IOError, ValueError, AttributeError, TypeError):
pass
return status_dict
def installed_interpreters_list(interpreter):
"""
Returns list of installed interpreters
:param interpreter: str - name of interpreter
:rtype: List of InterpreterSummary
"""
return [i for i in interpreter_versions_short_summary(interpreter) if i.installed]
def is_python_selector_installed():
"""
Checks that python selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- alt-python-virtualenv is installed
:rtype: bool
"""
return is_clpassenger_active() and is_virtualenv_installed()
def is_ruby_selector_installed():
"""
Checks that ruby selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- alt-python-virtualenv is installed
:rtype: bool
"""
return is_clpassenger_active() and is_virtualenv_installed()
def is_nodejs_selector_installed():
"""
Checks that nodejs selector is installed
Installed if:
- ea-apache24-mod-alt-passenger or alt-mod-passenger is installed
- At least one version is installed
:rtype: bool
"""
return is_clpassenger_active() and bool(installed_interpreters_list('nodejs'))
def is_php_selector_installed():
"""
Checks that php selector is installed
Installed if:
- CageFS is initialized
:rtype: bool
"""
return bool(check_cagefs_initialized())
def get_packages_with_lve_extensions():
"""
Gets packages with set lve limits via extension
"""
try:
result = WhmApiRequest('listpkgs').call()
except WhmApiError:
return []
lve_extensions_packages = [item['name'] for item in result['pkg']
if '_PACKAGE_EXTENSIONS' in item and item['_PACKAGE_EXTENSIONS'] == 'lve']
return lve_extensions_packages
def get_client_data_from_jwt_token(check_expiration=True) -> Optional[Dict[AnyStr, Union[AnyStr, bool]]]:
"""
Gets (if any) fields cl_plus and client_id from jwt token
:return: decoded jwt_token value, None if error
jwt_token: result of the successful decoding
"""
try:
with open(DEFAULT_JWT_ES_TOKEN_PATH, mode='rb') as file:
file_content = file.read().strip()
except (OSError, IOError):
return None
# JWT read success
try:
jwt_token = decode(file_content, algorithms=['HS256'],
options={'require_exp': True,
"verify_exp": check_expiration,
"verify_iss": True,
'verify_signature': False},
issuer='CloudLinux')
return jwt_token
# JWT format error
except exceptions.PyJWTError:
return None
def is_active_cloudlinux_license(token_data):
"""
Checks whether license ts is expired
"""
if not token_data:
return None
if not token_data.get('exp'):
return None
return int(time.time()) < int(token_data.get('exp'))
def get_cl_plus_sender_status() -> Optional[AnyStr]:
"""
Retrieves data from status of cl_plus_sender service
:return: status of service, Optional[AnyStr]
- 'active'
- 'inactive'
"""
try:
result = process_is_running(CL_PLUS_SENDER_FILE_PATH, False)
except FileNotFoundError:
result = False
return 'active' if result else 'inactive'
Zerion Mini Shell 1.0