# -*- coding: utf-8 -*-
# Command line arguments parser for cloudlinux-selector utility
# cloudlinux-license Utility to check/set Cloudlinux license
#
# 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 absolute_import
from __future__ import print_function
from __future__ import division
import json
import os
from docopt import docopt
from docopt import DocoptExit
from schema import Schema, And, Use, Or, SchemaError
from .selectorlib import CloudlinuxSelectorLib
NODEJS = 'nodejs'
PYTHON = 'python'
RUBY = 'ruby'
PHP = 'php'
def _ensure_command_allowed(interpreter, args, as_from_root, selector_status):
"""
Do some additional checks to restrict commands not available for current
user or interpreter or whatever and do this only after args parsing
"""
if not as_from_root and any([
args["install-version"],
args["uninstall-version"],
args["enable-version"],
args["disable-version"],
]):
raise SchemaError(
None, 'This command should be run from administrator only')
if interpreter != NODEJS and any([
args["change-version-multiple"],
]):
raise SchemaError(None, 'This command is supported only for NodeJS')
if interpreter != PYTHON and any([
args["import-applications"],
args["migrate"],
args["uninstall-modules"],
]):
raise SchemaError(None, 'This command is supported only for Python')
if interpreter not in (NODEJS, PYTHON) and any([
args["enable-version"],
args["disable-version"],
args["create"],
args["read-config"],
args["save-config"],
args["install-version"],
args["uninstall-version"],
args["start"],
args["restart"],
args["stop"],
args["destroy"],
args["install-modules"],
args["run-script"],
]):
raise SchemaError(None, 'This command is supported only for NodeJS and Python')
if interpreter != PHP and (args["make-defaults-config"] or args['setup']):
raise SchemaError(None, 'This command is supported only for %s' % PHP)
if not as_from_root and any([
args['make-defaults-config'],
args['setup'],
args['--selector-status'],
args['--default-version'],
args['--supported-versions']
]):
raise SchemaError(None, 'Specified option(s) only for root')
if interpreter != PYTHON and any([
args["--entry-point"]
]):
raise SchemaError(None, 'This options(s) only for Python')
if interpreter in (NODEJS, PYTHON) and not args['get'] and not selector_status and not _run_from_admin():
raise SchemaError(None, 'Selector is disabled')
def _run_from_admin():
"""
Check who is owner of the parent process.
if owner is root - return True
if parent process can't be found - return True
:return:
"""
try:
return os.stat(os.path.join('/proc/', str(os.getppid()))).st_uid == 0
except OSError:
return True
def parse_cloudlinux_selector_opts(argv, _is_json_need=False,
as_from_root=True):
"""
Parse arguments for cloudlinux-selector command
:param argv: sys.argv
:param _is_json_need: sys.argv contains --json key
:param as_from_root: True if we assume that root has called this util
:return cortege: (error_flag, s_message)
"""
# program name
prog_name = "cloudlinux-selector"
docstring = """Utility to get/set Cloudlinux selector options
Usage:
{0} [get] [--json] [--interpreter ] [(--get-supported-versions | --get-default-version | --get-selector-status)] [(--user | --domain )]
{0} get [--json] [--interpreter ] [--get-current-version] [--user ] [--domain ]
{0} set [--json] [--interpreter ] (--selector-status | --default-version | --supported-versions | --current-version ) [--user ] [--domain ]
{0} set [--json] [--interpreter ] --version (--extensions | --options ) [(--user | --domain )]
{0} set [--json] [--interpreter ] [(--user | --domain )] --app-root [--app-mode ] [--new-app-root ] [--new-domain ] [--new-app-uri ] [--new-version ] [--startup-file ] [--env-vars ] [--skip-web-check] [--entry-point ] [--config-files ] [--passenger-log-file ]
{0} create [--json] [--interpreter ] [(--user | --domain )] --app-root --app-uri [--version ] [--app-mode ] [--startup-file ] [--entry-point ] [--env-vars ] [--passenger-log-file ]
{0} set [--json] [--interpreter ] --reset-extensions --version [(--user | --domain )]
{0} (enable-version | disable-version) [--json] [--interpreter ] --version
{0} install-modules [--json] [--interpreter ] [(--user | --domain )] --app-root [(--requirements-file | --modules )] [--skip-web-check]
{0} uninstall-modules [--json] [--interpreter ] [(--user | --domain )] --app-root --modules
{0} install-version [--json] [--interpreter ] --version
{0} uninstall-version [--json] [--interpreter ] --version
{0} read-config [--json] [--interpreter ] [(--user | --domain )] --app-root --config-file
{0} save-config [--json] [--interpreter ] [(--user | --domain )] --app-root --config-file --content
{0} (start | restart | stop | destroy) [--json] [--interpreter ] [(--user | --domain )] --app-root
{0} run-script [--json] [--interpreter ] [(--user | --domain )] --app-root --script-name
{0} run-script [--json] [--interpreter ] [(--user | --domain )] --app-root --script-name -- ...
{0} change-version-multiple --json --interpreter --from-version --new-version
{0} make-defaults-config [--json] [--interpreter ]
{0} import-applications [--json] --interpreter
{0} migrate [--json] --interpreter --user --app-root
{0} setup [--json] --interpreter
{0} (-h | --help)
Options:
--json Return data in JSON format.
--interpreter One of php/nodejs/python, default is php
--get-supported-versions Return info about supported versions
--get-current-version Return current version of interpretator for user
--get-default-version Return info about default version only
--get-selector-status Return info about selector status
--reset-extensions Replace user extensions with version default extensions
--supported-versions Set supported versions of interpreter
--default-version Set default version of interpreter
--current-version Set alternative as user default
--selector-status Set selector status enabled or disabled
--version Version of interpreter
--extensions JSON dict with extensions and their status
--options JSON dict with options and their values
--user Username to operate on
--app-root Root of an application to be created
--domain Domain to work in
--app-uri URI path to get the application being created
--config-file path to config file to be read or saved
--config-files names of config files (such as requirements.txt or etc) (only for python interpreter)
--content Base64-encoded config file contents to be saved
--app-mode Application mode
--startup-file Startup application file
--env-vars Environment variable as json string
--new-app-root Set new application directory
--new-app-uri Set new application uri
--new-domain Set new domain for application
--new-version Set new nodejs version for application
--from-version Old NodeJS version for group change version operations
--script-name Command for an npm script to be run
--skip-web-check Skip check web application after change it's properties
--entry-point Use the specified entrypoint for application (only for python interpeter)
--requirements-file Use the specified file for install required modules
--modules Install comma-separated list of modules
--passenger-log-file Set passenger log full filename
-h, --help Show this help message and exit
""".format(prog_name)
try:
args = docopt(docstring, argv)
except DocoptExit:
s_error_string = 'ERROR: Invalid parameter passed'
if not _is_json_need:
s_error_string += "\n\n" + docstring
return False, s_error_string
interpreter = args.get('--interpreter')
def _convert_version(x):
"""For the NodeJS, ignore all the version parts except the major one"""
parts = x.split('.')
if interpreter == NODEJS:
return str(int(parts[0]))
if interpreter == PYTHON:
ver = x
if len(parts) == 3:
ver = x.rsplit('.', 1)[0]
return ver
return float(x) # keep old behavior for PHP
_version_validator = Or(None, And(str, lambda x: x == "native"), And(str, Use(_convert_version)),
error="Version must be Interpreter version number or native")
def _json_string_to_dict(s_json):
j_dict = json.loads(s_json)
# json.loads in some cases returns a string instead dictionary.
# So try to get keys to ensure that j_dict really dictionary
list(j_dict.keys())
return j_dict
s_json_error = '%s option should contain a valid JSON'
s = Schema({
"get": bool,
"set": bool,
"import-applications": bool,
"migrate": bool,
"create": bool,
"read-config": bool,
"enable-version": bool,
"disable-version": bool,
"save-config": bool,
"install-modules": bool,
"uninstall-modules": bool,
"start": bool,
"restart": bool,
"stop": bool,
"destroy": bool,
"install-version": bool,
"uninstall-version": bool,
"run-script": bool,
"change-version-multiple": bool,
"make-defaults-config": bool,
"setup": bool,
"": Or(None, list),
"--": bool,
"--json": bool,
"--help": bool,
"--interpreter": Or(None, PHP, PYTHON, RUBY, NODEJS),
"--get-supported-versions": bool,
"--get-current-version": bool,
"--get-default-version": bool,
"--get-selector-status": bool,
"--reset-extensions": bool,
"--supported-versions": Or(None, And(str, Use(_json_string_to_dict)),
error=s_json_error % "--supported-versions"),
"--default-version": _version_validator,
"--current-version": _version_validator,
"--selector-status": Or(None, And(str, lambda x: x in ["enabled", "disabled"]),
error="Selector status must be enabled or disabled"),
"--version": _version_validator,
"--extensions": Or(None, And(str, Use(_json_string_to_dict)),
error=s_json_error % "--extensions"),
"--options": Or(None, And(str, Use(_json_string_to_dict)),
error=s_json_error % "--options"),
"--user": Or(None, str),
"--app-root": Or(None, str),
"--domain": Or(None, str),
"--app-uri": Or(None, str),
"--config-file": Or(None, str),
"--content": Or(None, str),
"--app-mode": Or(None, str),
"--startup-file": Or(None, And(str, lambda x: x != "package.json"),
error='Cannot set "package.json" as startup file'),
"--env-vars": Or(None, str),
"--config-files": Or(None, str),
"--new-app-root": Or(None, str),
"--new-app-uri": Or(None, str),
"--new-domain": Or(None, str),
"--new-version": Or(None, str),
"--from-version": Or(None, str),
"--script-name": Or(None, str),
"--entry-point": Or(None, str),
"--requirements-file": Or(None, str),
"--modules": Or(None, And(str, lambda x: bool(x)),
error="modules should be a comma-separated list of packages"),
"--skip-web-check": bool,
"--passenger-log-file": Or(None, str)
})
def _check_users_part_cli(_args):
"""
Check args for existing of mandatory arguments
:param _args: parsed arguments from command line
:return: True if checking passed, False - not passed
"""
if interpreter not in (NODEJS, PYTHON):
return
if as_from_root and any((
_args['create'],
_args['read-config'],
_args['save-config'],
_args['install-modules'],
_args['uninstall-modules'],
_args['stop'],
_args['restart'],
_args['start'],
_args['destroy'],
_args['run-script'],
)) and not (_args['--user'] or _args['--domain']):
raise SchemaError(None, 'Domain or user argument is mandatory while calling selector under root')
if args["--interpreter"] == PYTHON and args['--entry-point'] and not args['--startup-file']:
raise SchemaError(None, '--entry-point option is requires --startup-file option for interpreter python')
if args["--app-mode"] and args["--interpreter"] != NODEJS:
raise SchemaError(None, '--app-mode option is requires only for interpreter nodejs')
if args["--entry-point"] and args["--interpreter"] != PYTHON:
raise SchemaError(None, '--entry-point option is requires only for interpreter python')
try:
args = s.validate(args)
except SchemaError as e:
return False, str(e)
if not args['--json'] and not args['import-applications']:
return False, "use --json option, other modes currently unsupported"
if interpreter is None or interpreter in (PHP, ):
args["--interpreter"] = PHP
selector_status = False
elif interpreter in (RUBY,):
msg = 'ruby interpreter currently unsupported. Use selectorctl instead'
return False, msg
elif interpreter in (PYTHON, NODEJS):
lib = CloudlinuxSelectorLib(args["--interpreter"])
selector_status = list(lib.get_selector_status().values())[0]
try:
_ensure_command_allowed(interpreter, args, as_from_root, selector_status)
_check_users_part_cli(args)
except SchemaError as e:
return False, str(e)
return True, args