[enh] Replace category by related_to fields

This commit is contained in:
ljf 2018-04-14 04:04:09 +02:00
parent 73828306df
commit 650b768229
6 changed files with 118 additions and 128 deletions

View file

@ -1614,16 +1614,18 @@ log:
action_help: List logs action_help: List logs
api: GET /logs api: GET /logs
arguments: arguments:
-l: -l:
full: --limit full: --limit
help: Maximum number of logs per categories help: Maximum number of logs
type: int type: int
--full:
help: Show more details
action: store_true
### log_display() ### log_display()
display: display:
action_help: Display a log content action_help: Display a log content
api: GET /logs/<file_name_list> api: GET /logs/<file_name_list>
arguments: arguments:
file_name_list: file_name:
help: Log filenames for which to display the content help: Log filenames for which to display the content
nargs: "*"

View file

@ -424,7 +424,7 @@ def app_map(app=None, raw=False, user=None):
return result return result
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def app_change_url(uo, auth, app, domain, path): def app_change_url(uo, auth, app, domain, path):
""" """
Modify the URL at which an application is installed. Modify the URL at which an application is installed.
@ -482,6 +482,8 @@ def app_change_url(uo, auth, app, domain, path):
env_dict["YNH_APP_NEW_DOMAIN"] = domain env_dict["YNH_APP_NEW_DOMAIN"] = domain
env_dict["YNH_APP_NEW_PATH"] = path.rstrip("/") env_dict["YNH_APP_NEW_PATH"] = path.rstrip("/")
if domain != old_domain:
uo.related_to.append(('domain', old_domain))
uo.extra.update({'env': env_dict}) uo.extra.update({'env': env_dict})
uo.start() uo.start()
@ -619,7 +621,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
# Start register change on system # Start register change on system
uo = UnitOperation('app_upgrade', 'app', app_instance_name, env=env_dict) related_to = [('app', app_instance_name)]
uo = UnitOperation('app_upgrade', related_to, env=env_dict)
# Execute App upgrade script # Execute App upgrade script
os.system('chown -hR admin: %s' % INSTALL_TMP) os.system('chown -hR admin: %s' % INSTALL_TMP)
@ -669,7 +672,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
return {"log": service_log('yunohost-api', number="100").values()[0]} return {"log": service_log('yunohost-api', number="100").values()[0]}
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def app_install(uo, auth, app, label=None, args=None, no_remove_on_failure=False): def app_install(uo, auth, app, label=None, args=None, no_remove_on_failure=False):
""" """
Install apps Install apps
@ -794,7 +797,7 @@ def app_install(uo, auth, app, label=None, args=None, no_remove_on_failure=False
# Execute remove script # Execute remove script
uo_remove = UnitOperation('remove_on_failed_install', uo_remove = UnitOperation('remove_on_failed_install',
'app', app_instance_name, [('app', app_instance_name)],
env=env_dict_remove) env=env_dict_remove)
remove_retcode = hook_exec( remove_retcode = hook_exec(
@ -843,7 +846,7 @@ def app_install(uo, auth, app, label=None, args=None, no_remove_on_failure=False
hook_callback('post_app_install', args=args_list, env=env_dict) hook_callback('post_app_install', args=args_list, env=env_dict)
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def app_remove(uo, auth, app): def app_remove(uo, auth, app):
""" """
Remove app Remove app
@ -1051,7 +1054,7 @@ def app_debug(app):
} }
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def app_makedefault(uo, auth, app, domain=None): def app_makedefault(uo, auth, app, domain=None):
""" """
Redirect domain root to an app Redirect domain root to an app
@ -1069,7 +1072,7 @@ def app_makedefault(uo, auth, app, domain=None):
if domain is None: if domain is None:
domain = app_domain domain = app_domain
uo.related_to['domain']=[domain] uo.related_to.append(('domain',domain))
elif domain not in domain_list(auth)['domains']: elif domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))

View file

@ -114,7 +114,7 @@ def _dyndns_available(provider, domain):
return r == u"Domain %s is available" % domain return r == u"Domain %s is available" % domain
@is_unit_operation('domain', lazy=True) @is_unit_operation(auto=False)
def dyndns_subscribe(uo, subscribe_host="dyndns.yunohost.org", domain=None, key=None): def dyndns_subscribe(uo, subscribe_host="dyndns.yunohost.org", domain=None, key=None):
""" """
Subscribe to a DynDNS service Subscribe to a DynDNS service
@ -127,9 +127,7 @@ def dyndns_subscribe(uo, subscribe_host="dyndns.yunohost.org", domain=None, key=
""" """
if domain is None: if domain is None:
domain = _get_maindomain() domain = _get_maindomain()
uo.related_to.append(('domain', domain))
uo.on = [domain]
uo.related_to['domain'] = [domain]
uo.start() uo.start()
# Verify if domain is provided by subscribe_host # Verify if domain is provided by subscribe_host
@ -176,7 +174,7 @@ def dyndns_subscribe(uo, subscribe_host="dyndns.yunohost.org", domain=None, key=
dyndns_installcron() dyndns_installcron()
@is_unit_operation('domain',lazy=True) @is_unit_operation(auto=False)
def dyndns_update(uo, dyn_host="dyndns.yunohost.org", domain=None, key=None, def dyndns_update(uo, dyn_host="dyndns.yunohost.org", domain=None, key=None,
ipv4=None, ipv6=None): ipv4=None, ipv6=None):
""" """
@ -219,21 +217,24 @@ def dyndns_update(uo, dyn_host="dyndns.yunohost.org", domain=None, key=None,
return return
else: else:
logger.info("Updated needed, going on...") logger.info("Updated needed, going on...")
uo.on = [domain] if domain is not None:
uo.related_to['domain'] = [domain] uo.related_to.append(('domain', domain))
uo.start()
# If domain is not given, try to guess it from keys available... # If domain is not given, try to guess it from keys available...
if domain is None: if domain is None:
(domain, key) = _guess_current_dyndns_domain(dyn_host) (domain, key) = _guess_current_dyndns_domain(dyn_host)
uo.related_to.append(('domain', domain))
uo.start()
# If key is not given, pick the first file we find with the domain given # If key is not given, pick the first file we find with the domain given
elif key is None: else:
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain)) uo.start()
if key is None:
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
if not keys: if not keys:
raise MoulinetteError(errno.EIO, m18n.n('dyndns_key_not_found')) raise MoulinetteError(errno.EIO, m18n.n('dyndns_key_not_found'))
key = keys[0] key = keys[0]
# This mean that hmac-md5 is used # This mean that hmac-md5 is used
# (Re?)Trigger the migration to sha256 and return immediately. # (Re?)Trigger the migration to sha256 and return immediately.

View file

@ -43,154 +43,140 @@ RELATED_CATEGORIES = ['app', 'domain', 'service', 'user']
logger = getActionLogger('yunohost.log') logger = getActionLogger('yunohost.log')
def log_list(limit=None): def log_list(limit=None, full=False):
""" """
List available logs List available logs
Keyword argument: Keyword argument:
limit -- Maximum number of logs per categories limit -- Maximum number of logs
""" """
result = {"categories": []} result = {"operations": []}
if not os.path.exists(OPERATIONS_PATH): if not os.path.exists(OPERATIONS_PATH):
return result return result
for category in sorted(os.listdir(OPERATIONS_PATH)): operations = filter(lambda x: x.endswith(METADATA_FILE_EXT), os.listdir(OPERATIONS_PATH))
result["categories"].append({"name": category, "operations": []}) operations = reversed(sorted(operations))
for operation in filter(lambda x: x.endswith(METADATA_FILE_EXT), os.listdir(os.path.join(OPERATIONS_PATH, category))):
base_filename = operation[:-len(METADATA_FILE_EXT)] if limit is not None:
md_filename = operation operations = operations[:limit]
md_path = os.path.join(OPERATIONS_PATH, category, md_filename)
operation = base_filename.split("-") for operation in operations:
operation_datetime = datetime.strptime(" ".join(operation[:2]), "%Y%m%d %H%M%S") base_filename = operation[:-len(METADATA_FILE_EXT)]
md_filename = operation
md_path = os.path.join(OPERATIONS_PATH, md_filename)
result["categories"][-1]["operations"].append({ operation = base_filename.split("-")
"started_at": operation_datetime,
"description": m18n.n("log_" + operation[2], *operation[3:]),
"name": base_filename,
"path": md_path,
})
result["categories"][-1]["operations"] = list(reversed(sorted(result["categories"][-1]["operations"], key=lambda x: x["started_at"]))) operation_datetime = datetime.strptime(" ".join(operation[:2]), "%Y%m%d %H%M%S")
if limit is not None: result["operations"].append({
result["categories"][-1]["operations"] = result["categories"][-1]["operations"][:limit] "started_at": operation_datetime,
"description": m18n.n("log_" + operation[2], *operation[3:]),
"name": base_filename,
"path": md_path,
})
return result return result
def log_display(file_name_list): def log_display(file_name):
""" """
Display full log or specific logs listed Display full log or specific logs listed
Argument: Argument:
file_name_list file_name
""" """
if not os.path.exists(OPERATIONS_PATH): if file_name.endswith(METADATA_FILE_EXT):
base_filename = file_name[:-len(METADATA_FILE_EXT)]
elif file_name.endswith(LOG_FILE_EXT):
base_filename = file_name[:-len(LOG_FILE_EXT)]
else:
base_filename = file_name
md_filename = base_filename + METADATA_FILE_EXT
md_path = os.path.join(OPERATIONS_PATH, md_filename)
log_filename = base_filename + LOG_FILE_EXT
log_path = os.path.join(OPERATIONS_PATH, log_filename)
operation = base_filename.split("-")
if not os.path.exists(md_path) and not os.path.exists(log_path):
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('log_does_exists', log=" ".join(file_name_list))) m18n.n('log_does_exists', log=file_name))
infos = {}
infos['description'] = m18n.n("log_" + operation[2], *operation[3:]),
infos['name'] = base_filename
result = {"operations": []} if os.path.exists(md_path):
with open(md_path, "r") as md_file:
try:
metadata = yaml.safe_load(md_file)
infos['metadata_path'] = md_path
infos['metadata'] = metadata
except yaml.YAMLError as exc:
print(exc)
for category in os.listdir(OPERATIONS_PATH): if os.path.exists(log_path):
for operation in filter(lambda x: x.endswith(METADATA_FILE_EXT), os.listdir(os.path.join(OPERATIONS_PATH, category))): with open(log_path, "r") as content:
if operation not in file_name_list and file_name_list: logs = content.read()
continue logs = [{"datetime": x.split(": ", 1)[0].replace("_", " "), "line": x.split(": ", 1)[1]} for x in logs.split("\n") if x]
base_filename = operation[:-len(METADATA_FILE_EXT)]
md_filename = operation
md_path = os.path.join(OPERATIONS_PATH, category, md_filename)
log_filename = base_filename + LOG_FILE_EXT
log_path = os.path.join(OPERATIONS_PATH, category, log_filename)
operation = base_filename.split("-")
with open(md_path, "r") as md_file:
try:
infos = yaml.safe_load(md_file)
except yaml.YAMLError as exc:
print(exc)
with open(log_path, "r") as content:
logs = content.read()
logs = [{"datetime": x.split(": ", 1)[0].replace("_", " "), "line": x.split(": ", 1)[1]} for x in logs.split("\n") if x]
infos['logs'] = logs
infos['description'] = m18n.n("log_" + operation[2], *operation[3:]),
infos['name'] = base_filename
infos['log_path'] = log_path infos['log_path'] = log_path
result['operations'].append(infos) infos['logs'] = logs
if len(file_name_list) > 0 and len(result['operations']) < len(file_name_list): return infos
logger.error(m18n.n('log_does_exists', log="', '".join(file_name_list)))
if len(result['operations']) > 0: def is_unit_operation(entities='app,domain,service,user', exclude='auth,password', operation_key=None, auto=True):
result['operations'] = sorted(result['operations'], key=lambda operation: operation['started_at'])
return result
def is_unit_operation(categorie=None, operation_key=None, lazy=False):
def decorate(func): def decorate(func):
def func_wrapper(*args, **kwargs): def func_wrapper(*args, **kwargs):
cat = categorie entities_list = entities.split(',')
exclude_list = exclude.split(',')
op_key = operation_key op_key = operation_key
on = None related_to = []
related_to = {}
inject = lazy
to_start = not lazy
if cat is None:
cat = func.__module__.split('.')[1]
if op_key is None: if op_key is None:
op_key = func.__name__ op_key = func.__name__
if cat in kwargs:
on = kwargs[cat] for entity in entities_list:
for r_category in RELATED_CATEGORIES: entity = entity.split(':')
if r_category in kwargs and kwargs[r_category] is not None: entity_type = entity[-1]
if r_category not in related_to: entity = entity[0]
related_to[r_category] = [] if entity in kwargs and kwargs[entity] is not None:
if isinstance(kwargs[r_category], basestring): if isinstance(kwargs[entity], basestring):
related_to[r_category] += [kwargs[r_category]] related_to.append({entity_type: kwargs[entity]})
else: else:
related_to[r_category] += kwargs[r_category] for x in kwargs[entity]:
related_to.append({entity_type: kwargs[x]})
context = kwargs.copy() context = kwargs.copy()
if 'auth' in context: for field in exclude_list:
context.pop('auth', None) if field in context:
uo = UnitOperation(op_key, cat, on, related_to, args=context) context.pop(field, None)
if to_start: uo = UnitOperation(op_key, related_to, args=context)
if auto:
uo.start() uo.start()
try: try:
if inject: if not auto:
args = (uo,) + args args = (uo,) + args
result = func(*args, **kwargs) result = func(*args, **kwargs)
finally: finally:
if uo.started_at is not None: uo.close(exc_info()[0])
uo.close(exc_info()[0])
return result return result
return func_wrapper return func_wrapper
return decorate return decorate
class UnitOperation(object): class UnitOperation(object):
def __init__(self, operation, category, on=None, related_to=None, **kwargs): def __init__(self, operation, related_to=None, **kwargs):
# TODO add a way to not save password on app installation # TODO add a way to not save password on app installation
self.operation = operation self.operation = operation
self.category = category
self.on = on
if isinstance(self.on, basestring):
self.on = [self.on]
self.related_to = related_to self.related_to = related_to
if related_to is None:
if self.category in RELATED_CATEGORIES:
self.related_to = {self.category: self.on}
self.extra = kwargs self.extra = kwargs
self.started_at = None self.started_at = None
self.ended_at = None self.ended_at = None
self.logger = None self.logger = None
self.path = os.path.join(OPERATIONS_PATH, category) self.path = OPERATIONS_PATH
if not os.path.exists(self.path): if not os.path.exists(self.path):
os.makedirs(self.path) os.makedirs(self.path)
@ -220,8 +206,8 @@ class UnitOperation(object):
def name(self): def name(self):
name = [self.started_at.strftime("%Y%m%d-%H%M%S")] name = [self.started_at.strftime("%Y%m%d-%H%M%S")]
name += [self.operation] name += [self.operation]
if self.on is not None: if self.related_to:
name += self.on name += self.related_to[0].values()
return '-'.join(name) return '-'.join(name)
@property @property
@ -229,10 +215,9 @@ class UnitOperation(object):
data = { data = {
'started_at': self.started_at, 'started_at': self.started_at,
'operation': self.operation, 'operation': self.operation,
'related_to': self.related_to
} }
if self.on is not None: if self.related_to is not None:
data['on'] = self.on data['related_to'] = self.related_to
if self.ended_at is not None: if self.ended_at is not None:
data['ended_at'] = self.ended_at data['ended_at'] = self.ended_at
data['success'] = self._success data['success'] = self._success

View file

@ -139,7 +139,7 @@ def tools_adminpw(auth, new_password):
logger.success(m18n.n('admin_password_changed')) logger.success(m18n.n('admin_password_changed'))
@is_unit_operation('domain', lazy=True) @is_unit_operation(auto=False)
def tools_maindomain(uo, auth, new_domain=None): def tools_maindomain(uo, auth, new_domain=None):
""" """
Check the current main domain, or change it Check the current main domain, or change it
@ -157,8 +157,7 @@ def tools_maindomain(uo, auth, new_domain=None):
if new_domain not in domain_list(auth)['domains']: if new_domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
uo.on = [new_domain] uo.related_to.append(('domain', new_domain))
uo.related_to['domain'] = [new_domain]
uo.start() uo.start()
# Apply changes to ssl certs # Apply changes to ssl certs
@ -471,7 +470,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
return {'packages': packages, 'apps': apps} return {'packages': packages, 'apps': apps}
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def tools_upgrade(uo, auth, ignore_apps=False, ignore_packages=False): def tools_upgrade(uo, auth, ignore_apps=False, ignore_packages=False):
""" """
Update apps & package cache, then display changelog Update apps & package cache, then display changelog
@ -711,7 +710,7 @@ def tools_port_available(port):
return False return False
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def tools_shutdown(uo, force=False): def tools_shutdown(uo, force=False):
shutdown = force shutdown = force
if not shutdown: if not shutdown:
@ -730,7 +729,7 @@ def tools_shutdown(uo, force=False):
subprocess.check_call(['systemctl', 'poweroff']) subprocess.check_call(['systemctl', 'poweroff'])
@is_unit_operation(lazy=True) @is_unit_operation(auto=False)
def tools_reboot(uo, force=False): def tools_reboot(uo, force=False):
reboot = force reboot = force
if not reboot: if not reboot:

View file

@ -90,7 +90,7 @@ def user_list(auth, fields=None):
return {'users': users} return {'users': users}
@is_unit_operation() @is_unit_operation('username:user')
def user_create(auth, username, firstname, lastname, mail, password, def user_create(auth, username, firstname, lastname, mail, password,
mailbox_quota="0"): mailbox_quota="0"):
""" """
@ -212,7 +212,7 @@ def user_create(auth, username, firstname, lastname, mail, password,
raise MoulinetteError(169, m18n.n('user_creation_failed')) raise MoulinetteError(169, m18n.n('user_creation_failed'))
@is_unit_operation() @is_unit_operation('username:user')
def user_delete(auth, username, purge=False): def user_delete(auth, username, purge=False):
""" """
Delete user Delete user
@ -248,7 +248,7 @@ def user_delete(auth, username, purge=False):
logger.success(m18n.n('user_deleted')) logger.success(m18n.n('user_deleted'))
@is_unit_operation() @is_unit_operation('username:user', exclude='auth,change_password')
def user_update(auth, username, firstname=None, lastname=None, mail=None, def user_update(auth, username, firstname=None, lastname=None, mail=None,
change_password=None, add_mailforward=None, remove_mailforward=None, change_password=None, add_mailforward=None, remove_mailforward=None,
add_mailalias=None, remove_mailalias=None, mailbox_quota=None): add_mailalias=None, remove_mailalias=None, mailbox_quota=None):