Mini Shell
# -*- coding: utf-8 -*-
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
#
import os
import logging
import subprocess
from packaging.version import Version
from clcommon import cpapi
from clcommon.cpapi.plugins.plesk import query_sql
from clcommon.public_hooks import CLOUDLINUX_HOOKS, CONTACT_SUPPORT_MESSAGE_FOOTER
BIN_DIR = os.path.join(CLOUDLINUX_HOOKS, 'plesk/')
PLESK_HOOK_REGISTER_FILE = '/usr/local/psa/bin/event_handler'
HOOKS = {
'phys_hosting_create': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'physical_hosting_created')},
'phys_hosting_update': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'physical_hosting_updated')},
'phys_hosting_delete': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'physical_hosting_deleted')},
'domain_update': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'domain_updated')},
'template_domain_update': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'plan_updated')},
'template_admin_update': {'SCRIPT_FILE': os.path.join(BIN_DIR, 'plan_updated')},
}
logger = logging.getLogger(__name__)
def plesk_get_event_handler_name_list(plesk_version, bin_dir=BIN_DIR):
"""
Get all installed events. Assume that we always have
no more than one executable file of each event type.
:param plesk_version: plesk cp version;
:param bin_dir: filter hooks by directory where
their executable is stored
:return: Dictionary of already installed events
{event_name: {handler_id, action_id, command}}
"""
if Version(plesk_version) < Version('17.8'):
sql = "SELECT a.name, eh.id, eh.action_id, eh.command " \
"FROM psa.event_handlers eh " \
"INNER JOIN psa.actions a ON a.id=eh.action_id"
else:
sql = "SELECT action_name, id, 0, command " \
"FROM psa.event_handlers"
rows = query_sql(sql)
result = {}
for row in rows:
# row[0] - action_name
# row[1] - handler id
# row[2] - action id (deprecated in PLESK 17.8 )
# row[3] - command
# take only hooks installed by this lib
if bin_dir in row[3]:
result[row[0]] = {
'handler_id': int(row[1]),
'action_id': int(row[2]),
'command': row[3]
}
return result
def plesk_hook_install(event_id, script_file):
"""
Plesk single hook install into /usr/local/bin
:param event_id: Event ID that must be created
:param script_file: Command for event
"""
if not os.path.isfile(script_file):
logger.error("File '%s' does not exist. "
"Maybe some rpm package is malformed. %s",
script_file, CONTACT_SUPPORT_MESSAGE_FOOTER)
return
try:
output = subprocess.check_output([
PLESK_HOOK_REGISTER_FILE, '--create',
'-command', script_file, '-priority', '50',
'-user', 'root', '-event', str(event_id)
], stderr=subprocess.STDOUT, text=True)
except (OSError, subprocess.CalledProcessError) as e:
if isinstance(e, subprocess.CalledProcessError):
message = e.output.rstrip('\n')
else:
message = str(e)
logger.error('failed to register Plesk hook %s: %s. %s',
script_file, message, CONTACT_SUPPORT_MESSAGE_FOOTER)
else:
logger.info('Register hook ended successfully; tool output: `%s`', output.rstrip())
def plesk_hook_remove(handler_id):
"""
Remove Plesk hook
:param handler_id: Handler ID of created event
:return: Nothing
"""
try:
output = subprocess.check_output([
PLESK_HOOK_REGISTER_FILE, '--delete', str(handler_id)
], stderr=subprocess.STDOUT, text=True)
except (OSError, subprocess.CalledProcessError) as e:
if isinstance(e, subprocess.CalledProcessError):
message = e.output.rstrip('\n')
else:
message = str(e)
logger.error('Failed to unregister Plesk hook %s. '
'Plesk reported following error: %s. %s',
handler_id, message, CONTACT_SUPPORT_MESSAGE_FOOTER)
else:
logger.info('Unregister hook ended successfully; tool output: `%s`', output.rstrip())
def define_hook_ids():
"""
Defines IDs of hooks by name (extends existing PLESK_HOOKS)
"""
hook_names_str = ','.join([f"'{i}'" for i in HOOKS])
sql = f"SELECT id, name FROM psa.actions WHERE name IN({hook_names_str})"
rows = query_sql(sql)
if not rows:
return
for row in rows:
HOOKS[row[1]]['EVENT_ID'] = int(row[0])
def install_hooks():
"""
Plesk hooks install into /usr/local/bin
"""
if not os.path.isfile(PLESK_HOOK_REGISTER_FILE):
logger.warning('%s does not exist; skip installing hooks', PLESK_HOOK_REGISTER_FILE)
return
panel_data = cpapi.get_cp_description()
plesk_version = panel_data['version']
if Version(plesk_version) < Version('17.8'):
define_hook_ids()
installed_events = plesk_get_event_handler_name_list(plesk_version)
for hook_name, hook_data in HOOKS.items():
# Checking of not installed events
if hook_name in installed_events:
logger.info('Hook %s is already registered in plesk; skip', hook_name)
continue
logger.debug('Registering %s action hook', hook_name)
if Version(plesk_version) < Version('17.8'):
plesk_hook_install(hook_data['EVENT_ID'], hook_data['SCRIPT_FILE'])
else:
plesk_hook_install(hook_name, hook_data['SCRIPT_FILE'])
def remove_hooks():
"""
All Plesk hooks remove
"""
if not os.path.isfile(PLESK_HOOK_REGISTER_FILE):
logger.warning('%s does not exist; skip installing hooks', PLESK_HOOK_REGISTER_FILE)
return
panel_data = cpapi.get_cp_description()
plesk_version = panel_data['version']
installed_events = plesk_get_event_handler_name_list(plesk_version)
for hook in HOOKS:
if hook not in installed_events:
logger.info('Hook %s is not registered in plesk; skip', hook)
continue
logger.debug('Unregistering %s action hook', hook)
plesk_hook_remove(installed_events[hook]['handler_id'])
# DOT NOT CHANGE OR REMOVE: USED IN CAGEFS
def remove_hook_by_str_id_and_location(event_name, bin_dir):
"""
Remove hooks of given
:param event_name: string id of hook type,
like 'phys_hosting_create'
:param bin_dir: directory where we should search
for hooks subscribed to given event name
:return: Nothing
"""
if not os.path.isfile(PLESK_HOOK_REGISTER_FILE):
return
panel_data = cpapi.get_cp_description()
plesk_version = panel_data['version']
installed_events = plesk_get_event_handler_name_list(plesk_version, bin_dir)
if event_name not in installed_events:
return
plesk_hook_remove(installed_events[event_name]['handler_id'])
Zerion Mini Shell 1.0