%PDF- %PDF-
Direktori : /opt/cloudlinux/venv/lib/python3.11/site-packages/clselect/ |
Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/clselect/clselectctlnodejsuser.py |
# -*- 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 from __future__ import print_function from __future__ import absolute_import from __future__ import division import os import pwd import requests import requests.packages.urllib3.exceptions as urllib_exceptions #pylint: disable=E0401 import shutil from requests.exceptions import ConnectionError from future.utils import iteritems from past.builtins import basestring from . import clpassenger from . import clselectctl from . import utils from clcommon.cpapi import userdomains from clcommon.utils import mod_makedirs from .clselectexcept import ClSelectExcept from .clselectnodejs.apps_manager import ApplicationsManager from .clselectnodejsuser import environments, interpreters from .baseclselect import APP_STARTED_CONST, APP_STOPPED_CONST from .clselectprint import clprint DEFAULT_STARTUP_FILE = 'app.js' DEFAULT_APP_STATE = APP_STARTED_CONST def _create_environment(user, directory, version, env_name=None, destroy_first=False): prefix = _get_prefix(user, directory) if not env_name: env_name = version environment = environments.Environment(env_name, user, prefix) if not environment.exists() or destroy_first: try: interpreter = interpreters.interpreters(key='version')[version] except KeyError: raise ClSelectExcept.NoSuchAlternativeVersion(version) environment.create(interpreter, destroy_first=destroy_first) return environment def _get_environment(user, directory, app_summary=None): prefix = _get_prefix(user, directory) if app_summary is None: user_summary = clpassenger.summary(user) try: app_summary = utils.get_using_realpath_keys(user, directory, user_summary) except KeyError: raise ClSelectExcept.NoSuchApplication( 'No such application (or application not configured) "%s"' % directory) binary = app_summary['binary'] env_name = os.path.basename(os.path.dirname(os.path.dirname(binary))) environment = environments.Environment(env_name, user, prefix) return environment def _get_prefix(user, directory): _, rel_dir = utils.get_abs_rel(user, directory) return os.path.join(environments.DEFAULT_PREFIX, rel_dir) def _ensure_version_enabled(version, user): """ Check whether particular interpreter version is enabled and raises exception if not :param user: user to include in exception """ from .clselectnodejs import node_manager if not node_manager.NodeManager().is_version_enabled(version): raise ClSelectExcept.UnableToSetAlternative( user, version, 'version is not enabled') def _get_existing_nesting_app_for(new_app_directory, user, apps_manager=None): """ Return None if new_app_directory is not nested inside an existing user application, and the name of conflicting application otherwise """ if apps_manager is None: apps_manager = ApplicationsManager() abs_app_path, _ = utils.get_abs_rel(user, new_app_directory) full_config = apps_manager.get_user_config_data(user) for existing_app in full_config.keys(): # In case home directory was symlinked after config was written existing_app_abs_path, _ = utils.get_abs_rel(user, existing_app) if (abs_app_path + os.sep).startswith(existing_app_abs_path + os.sep): return existing_app return None def create(user, directory, alias, version=None, doc_root=None, app_mode=None, env_vars=None, startup_file=None, domain_name=None, apps_manager=None, passenger_log_file=None): """ Create application :param user: unix user name :param directory: application path in user's home (app-root) :param alias: alias (app-uri) :param version: version of interpreter :param doc_root: doc_root :param app_mode: application mode for nodejs :param env_vars: dict with enviroment variables :param startup_file: main application file :param domain_name: domain name :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :param passenger_log_file: Passenger log filename to write to app's .htaccess :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() if version is None: raise ClSelectExcept.WrongData('Not passed version as argument') _ensure_version_enabled(version, user) if startup_file is None: startup_file = DEFAULT_STARTUP_FILE if env_vars is None: env_vars = {} if app_mode is None: app_mode = 'production' conflicting_app = _get_existing_nesting_app_for(directory, user, apps_manager) if conflicting_app is not None: raise ClSelectExcept.BusyApplicationRoot(conflicting_app) alias = clselectctl.get_alias(alias) environment = _create_environment(user, directory, version) binary = environment.interpreter().binary clpassenger.configure(user, directory, alias, apps_manager.INTERPRETER, binary, doc_root=doc_root, startup_file=startup_file, passenger_log_file=passenger_log_file) # webapp was started clpassenger.restart(user, directory) if not domain_name: # if domain name not defined - try to detemine it summary_data = clpassenger.summary(user) app_summary = utils.get_using_realpath_keys(user, directory, summary_data) domain_name = app_summary['domain'] app_data = { u'nodejs_version': version, u'domain': domain_name, u'app_uri': alias, u'app_status': DEFAULT_APP_STATE, u'startup_file': startup_file, u'app_mode': app_mode, u'config_files': [], u'env_vars': env_vars, } if passenger_log_file: app_data[u'passenger_log_file'] = passenger_log_file apps_manager.add_app_to_config(user, directory, app_data) try: apps_manager.add_env_vars_for_htaccess(user, directory, env_vars, doc_root) except Exception as err: clprint.print_diag('text', {'status': 'ERROR', 'message': str(err)}) def _create_symlink_to_node_modules(username, environment_path, app_root): """ Creates symlink to app's node_modules in app_root :param username: user name :param environment_path: Path to app's virtual environment :param app_root: Application root :return: None """ # This function logic is duplicated in npm wrapper, but we still need it here too. See commit # message of LVEMAN-1335 message = 'Cloudlinux NodeJS Selector demands to store node modules for application '\ 'in separate folder (virtual environment) pointed by symlink '\ 'called "node_modules". That`s why application should not contain folder/file '\ 'with such name in application root' link_name = os.path.join(pwd.getpwnam(username).pw_dir, app_root, 'node_modules') if os.path.islink(link_name): try: os.remove(link_name) except OSError: raise ClSelectExcept.RemoveSymlinkError( 'Can`t remove symlink %(link_name)s' % {'link_name': link_name} ) elif os.path.exists(link_name): # link_name not a old symlink, but exists raise ClSelectExcept.SymlinkError(message) link_to = os.path.join(environment_path, 'lib/node_modules') if not os.path.exists(link_to): mod_makedirs(link_to, 0o775) try: os.symlink(link_to, link_name) except OSError: raise ClSelectExcept.CreateSymlinkError('Error creating symlink. ' + message) def destroy(user, app_directory, doc_root, apps_manager=None): """ Destroy web app with specified directory for specified user :param user: username :param app_directory: application directory :param doc_root: Document root for selected domain :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() # get app state app_config = apps_manager.get_app_config(user, app_directory) # remove env from htaccess try: # if domain is already removed we shouldn't do anything if doc_root is not None: apps_manager.add_env_vars_for_htaccess(user, app_directory, None, doc_root) except Exception as err: clprint.print_diag('text', {'status': 'ERROR', 'message': str(err)}) if app_config.get('app_status') == APP_STOPPED_CONST: # stopped app user_home = pwd.getpwnam(user).pw_dir # Remove app's nodenv, e.g. /home/cltest1/nodevenv/test_app nodevenv_path = '/'.join([user_home, 'nodevenv', app_directory]) try: # Remove app's nodenv, e.g. /home/cltest1/nodevenv/test_app shutil.rmtree(nodevenv_path) except OSError: pass # if domain is already removed we shouldn't do anything if doc_root is not None: # Remove NodeJS lines from .htaccess htaccess_filename = apps_manager.get_htaccess_by_appdir(user, app_directory, doc_root) clpassenger.remove_passenger_lines_from_htaccess(htaccess_filename) # remove app from node-selector.json file apps_manager.remove_app_from_config(user, app_directory) # Update application status in passenger try: clpassenger.restart(user, app_directory) except ClSelectExcept.MissingApprootDirectory: pass return # Moved before removing an app from node-selector.json because in case if # app_root is absent we still want to remove venv dir of an application. # The method didn't anyhing if app_root is absent and skip other commands # including removal of a venv dir prefix = _get_prefix(user, app_directory) abs_dir, _ = utils.get_abs_rel(user, prefix) try: # Remove app's nodenv, e.g. /home/cltest1/nodevenv/test_app shutil.rmtree(abs_dir) except OSError: pass user_summary = clpassenger.summary(user) # remove app from node-selector.json file app_in_config = apps_manager.remove_app_from_config(user, app_directory) try: utils.get_using_realpath_keys(user, app_directory, user_summary) except KeyError: # if app was existed and app's dir is not exists, we skip all further actions if app_in_config: return None else: raise ClSelectExcept.WrongData("No such application (or application not configured) \"%s\"" % app_directory) # remove app from passenger clpassenger.unconfigure(user, app_directory) # Clear .htaccess try: clpassenger.restart(user, app_directory) except ClSelectExcept.MissingApprootDirectory: pass def start(user, app_directory, doc_root, apps_manager=None): """ Starts web app with specified directory for specified user :param user: username :param app_directory: application directory :param doc_root: Document root for selected domain :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() app_config = apps_manager.get_app_config(user, app_directory) if app_config is None: raise ClSelectExcept.WrongData("No such application (or application not configured) \"%s\"" % app_directory) # Do nothing if application already started if app_config.get('app_status') == APP_STARTED_CONST: return # Create .htaccess file for the application apps_manager.update_htaccess_file(user, app_directory, doc_root) # Save new application status in user's config apps_manager.set_app_status(user, app_directory, APP_STARTED_CONST) # Update application status in passenger clpassenger.restart(user, app_directory) def restart(user, app_directory, doc_root, apps_manager=None): """ Restarts web app with specified directory for specified user :param user: username :param app_directory: application directory :param doc_root: Document root for selected domain :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() app_config = apps_manager.get_app_config(user, app_directory) if app_config is None: raise ClSelectExcept.WrongData("No such application (or application not configured) \"%s\"" % app_directory) # If application was stopped - start it if app_config.get('app_status') == APP_STOPPED_CONST: start(user, app_directory, doc_root) else: clpassenger.restart(user, app_directory) def stop(user, app_directory, doc_root, apps_manager=None): """ Stops web app with specified directory for specified user :param user: username :param app_directory: application directory :param doc_root: Document root for selected domain :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() app_config = apps_manager.get_app_config(user, app_directory) if app_config is None: raise ClSelectExcept.WrongData("No such application (or application not configured) \"%s\"" % app_directory) # Do nothing if application is not started if app_config.get('app_status') == APP_STOPPED_CONST: return htaccess_filename = apps_manager.get_htaccess_by_appdir(user, app_directory, doc_root, app_config) # Remove NodeJS lines from .htaccess clpassenger.remove_passenger_lines_from_htaccess(htaccess_filename) # Save new application status in user's config apps_manager.set_app_status(user, app_directory, APP_STOPPED_CONST) # Update application status in passenger clpassenger.restart(user, app_directory) def check_response_from_webapp(domain, alias, action=None): """ Check response from user's webapp before and after calling action. Also compare both responses :param domain: domain associated with webapp :param alias: URI associated with webapp :param action: called action, that make something with webapp: install modules, transit it, etc :return: None """ app_is_inaccessible_before = 'Web application is inaccessible by its address "%s". The operation wasn\'t performed.' app_is_inaccessible_after = 'The operation was performed, but check availability of application has failed. ' \ 'Web application is inaccessible by its address "%s" after the operation.' app_is_broken = 'The operation was performed, but check availability of application has failed. ' \ 'Web application responds, but its return code "%s" or ' \ 'content type before operation "%s" doesn\'t equal to contet type after operation "%s".' requests.packages.urllib3.disable_warnings(urllib_exceptions.InsecureRequestWarning) #pylint: disable=E1101 if not callable(action): raise ClSelectExcept.WrongData('Wrong action for calling in checking webapp') webapp_url = 'https://{domain}/{alias}'.format( domain=domain, alias=alias, ) # for hiding from the module of Apache `mod_security` headers = { 'User-Agent': 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) ' 'Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13' } try: request = requests.get(webapp_url, verify=False, headers=headers) except ConnectionError: # site is not available by https protocol webapp_url = webapp_url.replace('https://', 'http://') try: request = requests.get(webapp_url, verify=False, headers=headers) except ConnectionError: raise ClSelectExcept.WebAppError(app_is_inaccessible_before % webapp_url) before_mime_type = request.headers.get('Content-Type') before_status = request.headers.get('Status') action() try: request = requests.get(webapp_url, verify=False, headers=headers) except ConnectionError: raise ClSelectExcept.WebAppError(app_is_inaccessible_after % webapp_url) after_mime_type = request.headers.get('Content-Type') after_status = request.headers.get('Status') # assume that app is broken if: # it's response Content-Type or Status code (if first code wasn't 500) changed # if last Status code was 500 (internal error) if before_mime_type.lower() != after_mime_type.lower() or \ (before_status != after_status and before_status is not None and before_status[:3] != '500') \ or (after_status is not None and after_status[:3] == '500'): raise ClSelectExcept.WebAppError(app_is_broken % (after_status, before_mime_type, after_mime_type)) def _get_info_about_webapp(app_summary=None, user=None): """ Get info (alias and domain) about user's web application :param app_summary: dict -> summary info about user's web application :param user: str -> name of unix user :return: tuple -> (alias, domain) """ if app_summary is None: raise ClSelectExcept.WrongData('Was passed incorrect summary info about application') if user is None: raise ClSelectExcept.WrongData('Was passed incorrect name of user') alias = app_summary['alias'] user_domains = userdomains(user) found_domains = [ domain for domain, doc_root in user_domains if utils.realpaths_are_equal(user, doc_root, app_summary['docroot'])] if len(found_domains) == 0: raise ClSelectExcept.WrongData('Can not found suitable domain for application') app_domain = found_domains[0] return alias, app_domain def install(user, directory, extension='-', skip_web_check=False, apps_manager=None): """ Install nodejs extension to user's webapp :param user: name os unix user :param directory: directory with webapp (app-root) :param extension: name and version of extension :param skip_web_check: skip check web application after change it's properties :param apps_manager: Application Manager. Class that responsible for gathering and writing information about applications :return: None """ if apps_manager is None: apps_manager = ApplicationsManager() user_config = apps_manager.get_user_config_data(user) try: app_data = utils.get_using_realpath_keys(user, directory, user_config) except KeyError: raise ClSelectExcept.WrongData('Record about application {} is absent'.format(directory)) if app_data['app_status'] != APP_STARTED_CONST: skip_web_check = True else: alias = app_data['app_uri'] app_domain = app_data['domain'] nodejs_version = app_data['nodejs_version'] cwd, _ = utils.get_abs_rel(user, directory) environment = _create_environment(user, directory, nodejs_version) def action(): # npm install environment.extension_install(extension=extension, cwd=cwd) clpassenger.restart(user, directory) if not skip_web_check: try: check_response_from_webapp( domain=app_domain, alias=alias, action=action, ) except ClSelectExcept.WebAppError as err: raise ClSelectExcept.WebAppError('An error occured during installation of modules. %s' % err) else: action() def summary(user): summ = {} for directory, data in iteritems(clpassenger.summary(user)): if data['interpreter'] != ApplicationsManager.INTERPRETER: continue environment = _get_environment(user, directory, data).as_deepdict() summ[directory] = { 'domain': data['domain'], 'alias': data['alias'], 'environment': environment['name'], 'interpreter': environment['interpreter'], } # add only list with additions domains if "domains" in data and len(data["domains"]) > 1: summ[directory]["domains"] = data["domains"] return summ def validate_env_vars(env_vars): if type(env_vars) is not dict: raise TypeError for item in env_vars: if not isinstance(env_vars[item], basestring): raise TypeError return env_vars