Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 18.189.170.65
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /opt/cloudlinux/venv/lib/python3.11/site-packages/clselector/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/cloudlinux/venv/lib/python3.11/site-packages/clselector//cl_selector.py
# coding:utf-8

# license.py - work code for cloudlinux-license utility
#
# 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


from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import fcntl
import sys
import time
import errno

import clcommon.cpapi as cpapi
import contextlib
import json
import os
import subprocess
import traceback

from typing import AnyStr  # NOQA
from future.utils import iteritems

from clcommon import ClPwd
from clcommon.clexception import FormattedException
from clcommon.mail_helper import MailHelper
from clcommon.clfunc import is_ascii_string
from cllicense import CloudlinuxLicenseLib

from clselect import clselectctl

from clselect.utils import get_abs_rel, mkdir_p, run_process_in_cagefs

from clselect.baseclselect import BaseSelectorError, AcquireApplicationLockError
from cli_utils import print_dictionary, replace_params
from clselect.clselectnodejs import CONFIG_DIR
from clselect.clselectnodejs.pkgmanager import PkgManager
from clselector.clpassenger_detectlib import is_clpassenger_active
from collections import defaultdict
from email.mime.text import MIMEText
from tempfile import mkstemp

from .cl_selector_arg_parse import NODEJS, PYTHON, PHP
from .cl_selector_arg_parse import parse_cloudlinux_selector_opts
from .selectorlib import CloudlinuxSelectorLib, OK_RES_DICT, ClSelectExcept

from clselect.clselectexcept import ClSelectExcept as ClSelectExcept_old


LOCK = '.lock'


# For unit tests
def _open(file_name, mode):
    return open(file_name, mode)


class CloudlinuxSelector(object):

    def __init__(self):
        self._is_json = False
        self._opts = {}
        self._selector_lib = None
        # For convenient checking during arg parsing and other operations.
        self._is_root_user = os.geteuid() == 0
        self._lock = None
        self._is_bkg_option_present = False
        self._bkg_option = '--background'
        self._nj_ver_move_from = ''
        self._pid_file_name = os.path.join(CONFIG_DIR, 'cloudlinux-selector_bkg.pid')

    def is_app_lock_needed(self):
        """
        Check if cloudlinux-selector called with application operations
        :return:  True if lock is need
        """
        # locking is implemented only for python and nodejs
        if self._opts['--interpreter'] not in [PYTHON, NODEJS]:
            return False

        if any([self._opts['change-version-multiple'], self._opts['create']]):
            return False
        if any([
            self._opts['start'],
            self._opts['restart'],
            self._opts['destroy'],
            self._opts['migrate'],
            self._opts['stop'],
            self._opts['install-modules'],
            self._opts['uninstall-modules'],
            self._opts['run-script'],
            self._opts['--app-mode'],
            self._opts['--env-vars'],
            self._opts['--new-app-root'],
            self._opts['--new-domain'],
            self._opts['--new-app-uri'],
            self._opts['--new-version'],
            self._opts['--startup-file']]):
            return True
        return False

    def acquire_app_lock_if_needed(
            self,
            ignore_missing_app_root=False,
            ignore_missing_doc_root=False,
    ):
        """
        Acquire lock for application if this lock is needed
        :return: None
        """
        if not self.is_app_lock_needed():
            return
        username, app_root = self._opts['--user'], self._opts['--app-root']
        _, app_venv = self._selector_lib.apps_manager.get_app_folders(
            username, app_root, chk_app_root=not ignore_missing_app_root,
            chk_env=not ignore_missing_doc_root)
        if not os.path.exists(app_venv):
            return

        lock_file = os.path.join(app_venv, LOCK)
        try:
            self._lock = open(lock_file, 'a+')
            fcntl.flock(self._lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError as e:
            if e.errno == errno.EDQUOT:
                reason = 'Disk quota exceeded. Please, free space and try again.'
                raise AcquireApplicationLockError(app_root, reason=reason)
            raise AcquireApplicationLockError(app_root)

    def send_notification_if_needed(self):
        if self._is_root_user and self._opts['--new-version']:
            self.send_notification()

    def send_notification(self):
        # NOTE(vlebedev): As of now, email notifications about selector changes don't contain enough info to be useful.
        #                 Moreover, as of the moment of writing, these messages are plain wrong as they always mention
        #                 only NodeJS, not the actual Selector being changed.
        #                 An investigation is required to clarify whether this functionality is needed at all
        #                 and - if yes - what pieces of information should be supplied in such notifications.
        #                 For more info, have a look at Jira:
        #                  * https://cloudlinux.atlassian.net/browse/LVEMAN-1904
        #                  * https://cloudlinux.atlassian.net/browse/LVEMAN-1903
        return

        MSG_TEMP = "NodeJS version for your application %s  was changed by admin. " \
                   "Please verify that application functions correctly."
        msg = MIMEText(MSG_TEMP % self._opts['--app-root'])

        me = 'CloudlinuxNodejsNotify@noresponse.com'
        msg['Subject'] = 'NodeJS version for your application %s  was changed by admin' % self._opts['--app-root']
        msg['From'] = me
        try:
            cp_userinfo = cpapi.cpinfo(
                self._opts['--user'],
                keyls=('mail', 'dns', 'locale', 'reseller'))[0]
            user_data_email = cp_userinfo[0]  # user's email

            msg['To'] = user_data_email

            mailhelper = MailHelper()
            mailhelper.sendmail(me, [user_data_email], msg)
        except (IndexError, KeyError, cpapi.cpapiexceptions.NotSupported):
            # can't get user mail or mail corrupted
            pass

    @staticmethod
    def parse_modules(modules_options):
        if not modules_options:
            return ()
        return [module for module in modules_options.strip().split(',') if module]

    def run(self, argv):
        """
        Run command action
        """
        self._is_json = "--json" in argv
        # Check background option
        self._is_bkg_option_present = self._bkg_option in argv
        if self._is_bkg_option_present:
            argv.remove(self._bkg_option)
        try:
            licence = CloudlinuxLicenseLib()
            if not licence.get_license_status():
                self._is_json = True
                return self._error_and_exit({"result": "Cloudlinux license isn't valid"})

            # get arguments, fill the value of --user argument if only --domain was given
            self._opts = self._parse_args(argv)
            self._selector_lib = CloudlinuxSelectorLib(self._opts['--interpreter'])
            self._selector_lib.check_selector_is_available()

            if self._selector_lib.should_be_runned_as_user(self._opts):
                with self._lock_interpreter_if_needed():
                    result = run_process_in_cagefs(
                        self._opts['--user'],
                        self._selector_lib.CLOUDLINUX_SELECTOR_UTILITY,
                        argv,
                    )
                    returncode = result['returncode']
                    self._print_raw_data(result['output'])
                    self.send_notification_if_needed()
                return returncode
            elif self._selector_lib.should_run_user_without_cagefs(self._opts):
                user_run_cmd = ['/usr/bin/sudo', '-u', self._opts['--user'],
                                self._selector_lib.CLOUDLINUX_SELECTOR_UTILITY] + argv
                with self._lock_interpreter_if_needed():
                    process = subprocess.Popen(user_run_cmd, env={})
                    process.communicate()
                    self.send_notification_if_needed()
                return process.returncode

            self.acquire_app_lock_if_needed(
                ignore_missing_app_root=self._opts['destroy'],
                ignore_missing_doc_root=self._opts['destroy'],
            )  # ignore app root and doc root for destroy option

            if self._opts['--passenger-log-file']:
                # Passenger log filename passed, check it
                message, log_filename = self._passenger_log_filename_validator(self._opts['--user'],
                                                                               self._opts['--passenger-log-file'])
                if message == "OK":
                    self._opts['--passenger-log-file'] = log_filename
                else:
                    self._error_and_exit(dict(result=message))
            if self._opts['set']:
                self.run_set()
            elif self._opts['migrate']:
                self.run_migrate_application()
            elif self._opts['import-applications']:
                self.run_import_applications()
            elif self._opts['create']:
                self.run_create()
            elif self._opts['destroy']:
                self.run_destroy()
            elif self._opts['start']:
                self.run_start()
            elif self._opts['restart']:
                self.run_restart()
            elif self._opts['stop']:
                self.run_stop()
            elif self._opts['read-config']:
                self.run_read_config()
            elif self._opts['save-config']:
                self.run_save_config()
            elif self._opts['install-modules']:
                self.run_install_modules()
            elif self._opts['uninstall-modules']:
                self.run_uninstall_modules()
            elif self._opts['install-version'] or self._opts['uninstall-version']:
                self.run_manage_version()
            elif self._opts['enable-version'] or self._opts['disable-version']:
                self.run_disable_or_enable_version()
            elif self._opts['run-script']:
                self._print_data(
                    self._selector_lib.run_script(
                        self._opts['--user'], self._opts['--app-root'],
                        self._opts['--script-name'], self._opts['<script_args>']
                    )
                )
            elif self._opts['change-version-multiple']:
                self._start_change_all_apps_versions()
            elif self._opts['make-defaults-config']:
                self._selector_lib.replace_mysqli()
            elif self._opts['setup']:
                self.run_setup()
            else:
                self.run_get()
        except (ClSelectExcept_old.ConfigNotFound,
                ClSelectExcept_old.WrongData,
                ClSelectExcept_old.NoSuchAlternativeVersion) as e:
            self._error_and_exit(dict(result=str(e)))
        except (ClSelectExcept_old.NativeNotInstalled,
                ClSelectExcept_old.MissingCagefsPackage) as e:
            if not self._opts['make-defaults-config']:
                # pylint: disable=exception-message-attribute
                self._error_and_exit(dict(result=e.message, context=e.context))
            # hack for alt-php spec that calls this method
            # just do not print error because it is not needed in rpm log
            exit(0)
        except ClSelectExcept_old.FileProcessError as e:
            self._error_and_exit(dict(result=e))
        except FormattedException as e:
            if e.details:
                self._error_and_exit(dict(result=e.message, context=e.context, details=e.details))
            else:
                self._error_and_exit(dict(result=e.message, context=e.context))
        except Exception as err:
            msg = traceback.format_exc()
            list_err_msg = traceback.format_exception_only(type(err), err)
            if isinstance(list_err_msg, list):
              err_msg = '\n'.join(list_err_msg)
            else:
              err_msg = list_err_msg
            self._error_and_exit(dict(
                result=err_msg,
                details=msg
            ))
        finally:
            if self._is_bkg_option_present:
                # If we worked in background remove pid file
                try:
                    os.remove(self._pid_file_name)
                except:
                    pass
        return 0

    def run_set(self):
        if self._opts['--default-version'] is not None:
            self._print_data(self._selector_lib.set_default_version(self._opts['--default-version']))
        elif self._opts['--current-version'] is not None:
            self._print_data(self._selector_lib.set_current_version(self._opts['--current-version']))
        elif self._opts['--reset-extensions']:
            self._print_data(self._selector_lib.reset_extensions(self._opts['--version']))
        elif self._opts['--selector-status'] is not None:
            self._print_data(self._selector_lib.set_selector_status(self._opts['--selector-status']))
        elif self._opts['--supported-versions'] is not None:
            self._print_data(self._selector_lib.set_supported_versions(self._opts['--supported-versions']))
        elif self._opts['--extensions'] is not None and self._opts['--version'] is not None:
            self._print_data(self._selector_lib.set_extensions(self._opts['--extensions'], self._opts['--version']))
        elif self._opts['--options'] is not None and self._opts['--version'] is not None:
            self._print_data(self._selector_lib.set_options(self._opts['--options'], self._opts['--version']))
        elif self._is_nodejs or self._is_python:
            self.run_change(self._opts['--user'], self._opts['--app-root'],
                            self._opts['--app-mode'],
                            self._opts['--env-vars'], self._opts['--new-app-root'], self._opts['--new-domain'],
                            self._opts['--new-app-uri'], self._opts['--new-version'], self._opts['--startup-file'],
                            self._opts['--skip-web-check'], self._opts['--entry-point'], self._opts['--config-files'],
                            self._opts['--passenger-log-file'])
        # XXX: should we return some error if no option was selected?

    def run_setup(self):
        self._selector_lib.setup_selector()

    def run_change(self, user, app_root, app_mode, env_vars, new_app_root, new_domain,
                   new_app_uri, new_version, startup_file, skip_web_check, entry_point, config_files,
                   passenger_log_file):
        """
        Call selectorctl to change application parameter
        :param config_files: names of config files (such as requirements.txt or etc) (only for python)
        :param entry_point: the specified entrypoint for application (only for python)
        :param user: application owner
        :param app_root: application main directory (application name)
        :param app_mode: application mode
        :param env_vars: dict with environment variables
        :param new_app_root: new application main directory (new application name)
        :param new_domain:  new application domain
        :param new_app_uri: new application uri
        :param new_version: new version for nodejs interpreter
        :param startup_file: new startup file for application
        :param skip_web_check: skip check web application after change it's properties
        :param passenger_log_file: Passenger log filename
        :return: None
        """
        if user is None:
            self._error_and_exit({
                'result': 'ERROR: User is not specified'})
        if new_app_root is not None:
            # Change app-root
            r = self._selector_lib.relocate(user, app_root, new_app_root)
            # after relocate we need to change current app_root to new one
            app_root = new_app_root
            if r['status'].upper() != 'OK':
                self._print_data(r)
                sys.exit(1)
        if new_app_uri is not None or new_domain is not None:
            # Change app-uri
            r = self._selector_lib.transit(user, app_root, new_app_uri, new_domain)
            if r['status'].upper() != 'OK':
                self._print_data(r)
                sys.exit(1)
        if any((app_mode, env_vars, startup_file, entry_point, config_files is not None,
                passenger_log_file is not None)):
            # create list of config files
            if config_files is not None:
                config_files = [item for item in config_files.split(',') if item != '']
            # Change app-mode, environment variables or startup file
            r = self._selector_lib.set_variables(user, app_root, app_mode, env_vars,
                                                 startup_file, entry_point, config_files, passenger_log_file)
            if r['status'].upper() != 'OK':
                self._print_data(r)
                sys.exit(1)
        if new_version is not None:
            # Change interpreter version for application
            r = self._selector_lib.change_version(user, app_root, new_version, skip_web_check)
            if r['status'].upper() != 'OK':
                self._print_data(r)
                sys.exit(1)
        # print_data create {status:ok, timestamp:} and print it
        self._print_data({})

    def run_import_applications(self):
        self._print_data(self._selector_lib.run_import_applications())

    def run_migrate_application(self):
        self._print_data(self._selector_lib.run_migrate_application(
            self._opts['--user'], self._opts['--app-root']))

    def run_get(self):
        if self._opts['--get-default-version']:
            self._print_data(self._selector_lib.get_default_version())
        elif self._opts['--get-selector-status']:
            self._print_data(self._selector_lib.get_selector_status())
        elif self._opts['--get-supported-versions']:
            self._print_data(self._selector_lib.get_supported_versions())
        elif self._opts['--get-current-version']:
            self._print_data(self._selector_lib.get_current_version(self._opts['--user']))
        elif self._opts['--interpreter'] == PHP:
            self._print_data(self._selector_lib.get_full())
        else:
            res = {'passenger_active': is_clpassenger_active()}
            if self._opts['--interpreter'] == NODEJS:
                res.update(self._selector_lib.get_apps_users_info(self._opts['--user']))
                # Applications count from background process
                remaining_apps_count, total_apps_count = self._get_apps_count_from_pid_file()
                if remaining_apps_count is not None and total_apps_count is not None:
                    res['remaining_apps_count'] = remaining_apps_count
                    res['total_apps_count'] = total_apps_count
            elif self._opts['--interpreter'] == PYTHON:
                res.update(self._selector_lib.get_apps_users_info(self._opts['--user']))
            if 'result' in res:
                self._print_data(res, result=res['result'])
            else:
                self._print_data(res)

    def run_create(self):
        # Not allow to create application on locked version
        if self._is_version_locked_by_background_process(self._opts['--version']):
            self._error_and_exit({
                'result': 'Can\'t create application: Nodejs version %(version)s is locked by background process',
                'context': {'version': self._opts['--version']},
            })
        if not is_clpassenger_active():
            # passenger not active, application creation not allowed
            if self._opts['--interpreter'] == PYTHON:
                url = 'https://docs.cloudlinux.com/python_selector/#installation'
            else:
                url = 'https://docs.cloudlinux.com/index.html?installation.html'
            self._error_and_exit({
                'result': 'Application creation not allowed, '
                          'Phusion Passenger seems absent, please see %(url)s for details',
                'context': {
                    'url': url
                },
            })

        self._print_data(
            self._selector_lib.create_app(
                self._opts['--app-root'],
                self._opts['--app-uri'],
                self._opts['--version'],
                self._opts['--user'],
                self._opts['--domain'],
                self._opts['--app-mode'],
                self._opts['--startup-file'],
                self._opts['--env-vars'],
                self._opts['--entry-point'],
                self._opts['--passenger-log-file']
            ))

    def run_destroy(self):
        self._print_data(self._selector_lib.destroy_app(self._opts['--app-root'],
                                                        self._opts['--user']))

    def run_start(self):
        self._print_data(self._selector_lib.start_app(self._opts['--app-root'],
                                                      self._opts['--user']))

    def run_restart(self):
        self._print_data(self._selector_lib.restart_app(self._opts['--app-root'],
                                                        self._opts['--user']))

    def run_stop(self):
        self._print_data(self._selector_lib.stop_app(self._opts['--app-root'],
                                                     self._opts['--user']))

    def run_read_config(self):
        self._print_data(
            self._selector_lib.read_app_config(
                self._opts['--app-root'],
                self._opts['--config-file'],
                self._opts['--user']))

    def run_save_config(self):
        self._print_data(
            self._selector_lib.save_app_config(
                self._opts['--app-root'],
                self._opts['--config-file'],
                self._opts['--content'],
                self._opts['--user']))

    def run_install_modules(self):
        self._print_data(
            self._selector_lib.install_modules(
                self._opts['--app-root'],
                user=self._opts['--user'],
                domain=self._opts['--domain'],
                skip_web_check=self._opts['--skip-web-check'],
                spec_file=self._opts['--requirements-file'],
                modules=self.parse_modules(self._opts['--modules']),
            )
        )

    def run_uninstall_modules(self):
        self._print_data(
            self._selector_lib.uninstall_modules(
                self._opts['--app-root'],
                modules=self.parse_modules(self._opts['--modules']),
                user=self._opts['--user'],
                domain=self._opts['--domain'],
                skip_web_check=self._opts['--skip-web-check'],
            )
        )

    def run_disable_or_enable_version(self):
        """
        Disable or enable interpreter version
        :return: None
        """

        version = self._opts['--version']
        target_version_status = self._opts['enable-version']
        try:
            self._print_data(self._selector_lib.set_version_status(target_version_status, version))
        except BaseSelectorError as e:
            self._error_and_exit({
                'result': str(e),
            })

    def run_manage_version(self):
        ver = str(self._opts['--version'])
        try:
            if self._opts['install-version']:
                res = self._selector_lib.selector_manager.install_version(ver)
            else:
                res = self._selector_lib.selector_manager.uninstall_version(ver)
        except Exception as e:
            res = str(e)
        if res is None:
            self._print_data(OK_RES_DICT)
        elif isinstance(res, dict):
            self._error_and_exit(res)
        else:
            self._error_and_exit({'result': res})

    def _parse_args(self, argv):
        """
        Parse CLI arguments
        """
        status, data = parse_cloudlinux_selector_opts(
            argv, self._is_json, as_from_root=self._is_root_user)
        if not status:
            # exit with error if can`t parse CLI arguments
            self._error_and_exit(replace_params(data))

        # For php we check only user exists
        if data['--interpreter'] == 'php':
            if data['--user']:
                try:
                    pwd = ClPwd()
                    pwd.get_pw_by_name(data['--user'])
                except ClPwd.NoSuchUserException:
                    raise ClSelectExcept(
                        {
                            'message': 'No such user (%s)',
                            'context': {
                                'user': data['--user']
                            },
                        }
                    )
            return data

        # We can't detect CPanel under user in CageFS, so we check CPanel specific directory /usr/local/cpanel
        # In cageFS this directory present, but we can't read it content
        # May be this is temporary solution, possibly change after PTCCLIB-170
        if not os.path.isdir('/usr/local/cpanel') and (data['import-applications'] or data['migrate']):
            self._error_and_exit({'result': 'success',
                                  'warning': 'Import/migrate of Python Selector applications is not supported'})

        # try to resolve username (e.g. if only domain was specified in cli)
        # DO NOT RESOLVE DOMAIN HERE!
        # it leads to confusion between the "user's main domain"
        # and the "domain where application works"
        data['--user'], _ = CloudlinuxSelectorLib.safely_resolve_username_and_doc_root(
            data['--user'], data['--domain'])

        # validate app_root before passing it to create & transit methods
        # to make them 'safe' and avoid code duplicates
        for app_root_arg in ['--app-root', '--new-app-root']:
            if not data.get(app_root_arg):
                continue

            _, directory = get_abs_rel(data['--user'], data[app_root_arg])
            try:
                # directory name must not be one of the reserved names and
                # should not contain invalid symbols.
                clselectctl.check_directory(directory)
            except ValueError as e:
                self._error_and_exit(dict(
                    result=str(e)
                ))
            data[app_root_arg] = directory
        return data

    def _error_and_exit(self, message, error_code=1):
        """
        Print error and exit
        :param dict message: Dictionary with keys "result" as string and optional "context" as dict
        """
        if "status" in message:
            message["result"] = message["status"]
            del(message["status"])
        if self._is_json:
            message.update({"timestamp": time.time()})
            print_dictionary(message, True)
        else:
            try:
                print(str(message["result"]) % message.get("context", {}))
            except KeyError:
                print("Error: %s" % message)
        sys.exit(error_code)

    @staticmethod
    def _print_raw_data(data):
        # type: (AnyStr) -> None
        """
        Print raw data.
        Function should be used in case if you want
        to print a json string as an output from other utilities
        """

        print(data)

    def _print_data(self, data, force_json=False, result="success"):
        """
        Output data wrapper
        :param: `dict` data - data for output to stdout
        :param: `bool` force_json - always output json format
        """
        if isinstance(data, dict):
            data = data.copy()
            # data may be Exception object with data and context inside
            if "data" in data and isinstance(data["data"], dict):
                data = data["data"]
            # data may already contain "status", so we wont rewrite it
            data.setdefault("status", result)

            # rename "status": "ok" to "result": "success"
            if data["status"].lower() == "ok":
                data["result"] = "success"
                if self._opts['--interpreter'] == PHP and self._selector_lib.check_multiphp_system_default():
                    data['warning'] = 'MultiPHP system default PHP version is alt-php. ' \
                                      'PHP Selector does not work and should be disabled!'

            # do not set result to status, if result was passed
            elif 'result' not in data and 'status' in data:
                data["result"] = data["status"]
            del(data["status"])
            # and do update timestamp with current time
            data.update({"timestamp": time.time()})
        print_dictionary(data, self._is_json or force_json)

    @property
    def _is_nodejs(self):
        return self._opts['--interpreter'].lower() == NODEJS

    @property
    def _is_python(self):
        return self._opts['--interpreter'].lower() == PYTHON

    def _is_interpreter_lock_needed(self):
        # Only NodeJs & Python has interpreter locking
        if self._opts['--interpreter'] in [NODEJS, PYTHON]:
            # We will lock only new version because old is unknown before
            # we SU to user and read it's app configs. We can implement ugly
            # workaround later if someone ask it
            new_version = self._opts['--new-version']
            return bool(new_version)
        return False

    @contextlib.contextmanager
    def _lock_interpreter_if_needed(self):
        """
        Wrapper over contextmanager of PkgManager in order not
        to try acquire lock when it is not needed.
        """
        if self._is_interpreter_lock_needed():
            # TODO: we need to simplify access and usage
            # of apps_manager / pkg_manager  methods
            mgr = self._selector_lib.apps_manager
            with mgr.acquire_interpreter_lock(self._opts['--new-version']):
                yield
        else:
            yield

    def _get_nj_versions(self):
        """
        Retrives NodeJS versions from arguments and converts them to major versions
        :return: Cortege (from_version, to_version)
        """
        from_version = self._opts['--from-version']
        to_version = self._opts['--new-version']
        from_version = self._selector_lib.get_major_version_from_short(from_version)
        to_version = self._selector_lib.get_major_version_from_short(to_version)
        if from_version == to_version:
            self._error_and_exit({'result': '--from-version and --new-version should be different'})
        return from_version, to_version

    def _check_environment_for_move_apps(self):
        """
        Checks arguments and environment before start group applications move
        :return: Cortege (from_version, to_version)
        """
        from_version, to_version = self._get_nj_versions()
        pkg_manager = PkgManager()
        installed_nj_versions = pkg_manager.installed_versions
        if to_version not in installed_nj_versions:
            self._error_and_exit({
                'result': 'Can\'t move NodeJS applications to Nodejs version %(version)s. No such version installed.',
                'context': {'version': to_version},
            })
        # For running process: print error if we trying to start background process and another one already running
        if not self._is_bkg_option_present and self._is_background_process_already_running():
            self._error_and_exit({'result': 'Another background process already started.'})
        return from_version, to_version

    def _start_change_all_apps_versions(self):
        """
        Change all applications all users versions
        :return:
        """
        from_version, to_version = self._check_environment_for_move_apps()
        # No background process running
        if not self._is_bkg_option_present:
            # Option --background not specified, start background process
            # For example:
            # cloudlinux-selector change-version-multiple --json --interpreter=nodejs --from-version=6 --new-version=9 --background
            command = "%s change-version-multiple --json --interpreter=nodejs --from-version=%s --new-version=%s %s >/dev/null &" %\
                      (self._selector_lib.CLOUDLINUX_SELECTOR_UTILITY, from_version, to_version, self._bkg_option)
            subprocess.run(command, shell=True, executable='/bin/bash')
            # Exit without process end waiting
            self._print_data(OK_RES_DICT)
            return
        # Option --background specified, start move application
        # Scan all users/apps, build appliction list to move
        users_apps_list, total_apps_count = self._get_all_apps_by_version(from_version)
        # Do nothing if application list is empty
        if not users_apps_list or total_apps_count == 0:
            return
        # Create pid file for background process
        self._write_pid_file(from_version, to_version, total_apps_count)
        # Move applications
        self._move_apps_by_list(users_apps_list, to_version, total_apps_count)

    def _move_apps_by_list(self, apps_dict, to_version, total_apps_count):
        """
        Move applications from list from one NodeJS version to another
        :type dict
        :param apps_dict: Application list. List example:
            {'cltest1': [u'modjsapp_root'], 'cltest2': [u'app2', u'main_app']}
        :param to_version: Move applications to this version
        :param total_apps_count: Total applications count for move
        :return: None
        """
        for user_name, user_app_list in iteritems(apps_dict):
            for app_root in user_app_list:
                # cloudlinux-selector set --json --interpreter nodejs  --user <str> --app-root <str> --new-version <str>
                cmd = [ self._selector_lib.CLOUDLINUX_SELECTOR_UTILITY, 'set', '--json', '--interpreter', NODEJS,
                        '--user', user_name, '--app-root', app_root, '--new-version', to_version ]
                process = subprocess.Popen(cmd)
                process.communicate()
                total_apps_count -= 1
                # update pid file
                self._change_pid_file(total_apps_count)
                time.sleep(30)

    def _get_all_apps_by_version(self, from_version):
        """
        Retrives list of all NodeJS applications for all users, which uses supplied version of NodeJS
        :param from_version: Required NodeJS version
        :return: Cortege: (application_list, application_count). Example:
            ({'cltest1': [u'modjsapp_root'], 'cltest2': [u'app2', u'main_app']}, 3)
        """
        users_apps_dict = defaultdict(list)
        # 0 -- we always root here
        user_info = self._selector_lib.apps_manager.get_users_dict()
        total_apps_count = 0
        for user_name, user_pw_entry in iteritems(user_info):
            try:
                user_app_data = self._selector_lib.apps_manager.read_user_selector_config_json(
                    user_pw_entry.pw_dir,
                    user_pw_entry.pw_uid,
                    user_pw_entry.pw_gid,
                )
                # user_app_data example:
                # {u'modjsapp_root': {u'domain': u'cltest1.com', u'app_uri': u'modjsappuri', u'nodejs_version': u'8',
                #                     u'app_status': u'started', u'env_vars': {}, u'app_mode': u'production',
                #                     u'config_files': [], u'startup_file': u'app.js'}}
                for app_root, app_info in iteritems(user_app_data):
                    # if application on from_version - add it to list for move
                    if app_info['nodejs_version'] == from_version:
                        users_apps_dict[user_name].append(app_root)
                        total_apps_count += 1
            except (BaseSelectorError, TypeError, KeyError, AttributeError):
                # Skip user if config is unreadable
                continue
        return users_apps_dict, total_apps_count

    def _is_background_process_already_running(self):
        """
        Determine is background process already working
        :return: True|False
        """
        try:
            data = json.load(_open(self._pid_file_name, 'r'))
            self._nj_ver_move_from = data['from_version']
            return True
        except:
            pass
        # No background process found
        return False

    def _is_version_locked_by_background_process(self, nj_version):
        """
        Checks if NodeJS version blocked by background operation
        :param nj_version: NodeJS version to check
        :return: True - version is locked, False - not locked
        """
        if self._opts['--interpreter'] == PYTHON:
            return False
        # Check version and use default version if need
        nj_version = self._selector_lib.resolve_version(nj_version)
        nj_version = self._selector_lib.get_major_version_from_short(nj_version)
        is_bkg_process_present = self._is_background_process_already_running()
        if is_bkg_process_present and nj_version == self._nj_ver_move_from:
            return True
        return False

    def _write_pid_file(self, from_version, to_version, total_apps_count):
        """
        Creates pid file for background process move version from version to version
        :param from_version: Move from NJ version
        :param to_version: Move to NJ version
        :param total_apps_count: Total application count to move
        :return: None
        """
        json.dump({
            'pid': os.getpid(),
            'from_version': str(from_version),
            'to_version': str(to_version),
            'total_apps_count': total_apps_count,
            'remaining_apps_count': total_apps_count,
            'time': float(time.time()),
        }, _open(self._pid_file_name, 'w'))
        # Make file readable by anyone
        os.chmod(self._pid_file_name, 0o644)

    def _read_pid_file(self):
        """
        Reads pid file and returns it's content as dictionary
        :return: Dictionary
        """
        f = _open(self._pid_file_name, 'r')
        pid_data = json.load(f)
        f.close()
        return pid_data

    def _change_pid_file(self, remaining_apps_count):
        """
        Creates pid file for background process move version from version to version
        :param remaining_apps_count: Remaining application count to move
        :return: None
        """
        try:
            pid_data = self._read_pid_file()
            pid_data['remaining_apps_count'] = remaining_apps_count

            _, temp_file_name = mkstemp(dir=CONFIG_DIR)
            json.dump(pid_data, _open(temp_file_name, 'w'))
            os.rename(temp_file_name, self._pid_file_name)
            # Make file readable by anyone
            os.chmod(self._pid_file_name, 0o644)
        except (OSError, IOError, KeyError):
           return

    def _get_apps_count_from_pid_file(self):
        """
        Retrieves application counts from pid file
        :return: Cortege (remaining_apps_count, total_apps_count)
            If no background process started, returns None, None
        """
        try:
            f = _open(self._pid_file_name, 'r')
            pid_data = json.load(f)
            f.close()
            return pid_data['remaining_apps_count'], pid_data['total_apps_count']
        except (OSError, IOError, KeyError):
            return None, None

    @staticmethod
    def _passenger_log_filename_validator(username, log_filename):
        """
        Validates passenger log file name
        :param username: User's name
        :param log_filename: passenger log file name to validate
        :return: tuple: (message, log_filename).
            message: "OK" - filename is valid, any other string - invalid, error text
            log_filename: corrected log filename - simlink dereferencing, appends user's homedir for relative paths, etc
        """
        pwd = ClPwd()
        user_homedir = pwd.get_homedir(username)
        try:
            if not is_ascii_string(log_filename):
                return "ERROR: Passenger log filename should contain only english letters", None
            if os.path.isdir(log_filename):
                return "ERROR: Passenger log file should be a filename, not a directory name", None
            if not log_filename.startswith(os.path.sep):
                log_filename = os.path.join(user_homedir, log_filename)
            log_realpath = os.path.realpath(log_filename)
            if log_realpath.startswith(user_homedir+os.sep):
                dirname = os.path.dirname(log_realpath)
                if not os.path.exists(dirname):
                    mkdir_p(dirname)
                return "OK", log_realpath
        except (OSError, IOError) as exc:
            return "%s" % str(exc), None
        return "ERROR: Passenger log file should be placed in user's home", None

Youez - 2016 - github.com/yon3zu
LinuXploit