[enh] Set env var for each app script and rename app variables

This commit is contained in:
Julien Malik 2016-03-14 09:27:05 +01:00 committed by Jérôme Lebleu
parent 7ce7d6fc15
commit c5d1ef981b
3 changed files with 150 additions and 60 deletions

View file

@ -35,6 +35,7 @@ import socket
import urlparse import urlparse
import errno import errno
import subprocess import subprocess
from collections import OrderedDict
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
@ -56,6 +57,10 @@ re_github_repo = re.compile(
'(/tree/(?P<tree>.+))?' '(/tree/(?P<tree>.+))?'
) )
re_app_instance_name = re.compile(
r'^(?P<appid>[\w]+?)(__(?P<appinstancenb>[1-9][0-9]*))?$'
)
def app_listlists(): def app_listlists():
""" """
@ -344,62 +349,70 @@ def app_upgrade(auth, app=[], url=None, file=None):
elif not isinstance(app, list): elif not isinstance(app, list):
app = [ app ] app = [ app ]
for app_id in app: for app_instance_name in app:
installed = _is_installed(app_id) installed = _is_installed(app_instance_name)
if not installed: if not installed:
raise MoulinetteError(errno.ENOPKG, raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app_id)) m18n.n('app_not_installed', app=app_instance_name))
if app_id in upgraded_apps: if app_instance_name in upgraded_apps:
continue continue
current_app_dict = app_info(app_id, raw=True) current_app_dict = app_info(app_instance_name, raw=True)
new_app_dict = app_info(app_id, raw=True) new_app_dict = app_info(app_instance_name, raw=True)
if file: if file:
manifest = _extract_app_from_file(file) manifest = _extract_app_from_file(file)
elif url: elif url:
manifest = _fetch_app_from_git(url) manifest = _fetch_app_from_git(url)
elif new_app_dict is None or 'lastUpdate' not in new_app_dict or 'git' not in new_app_dict: elif new_app_dict is None or 'lastUpdate' not in new_app_dict or 'git' not in new_app_dict:
logger.warning(m18n.n('custom_app_url_required', app=app_id)) logger.warning(m18n.n('custom_app_url_required', app=app_instance_name))
continue continue
elif (new_app_dict['lastUpdate'] > current_app_dict['lastUpdate']) \ elif (new_app_dict['lastUpdate'] > current_app_dict['lastUpdate']) \
or ('update_time' not in current_app_dict['settings'] \ or ('update_time' not in current_app_dict['settings'] \
and (new_app_dict['lastUpdate'] > current_app_dict['settings']['install_time'])) \ and (new_app_dict['lastUpdate'] > current_app_dict['settings']['install_time'])) \
or ('update_time' in current_app_dict['settings'] \ or ('update_time' in current_app_dict['settings'] \
and (new_app_dict['lastUpdate'] > current_app_dict['settings']['update_time'])): and (new_app_dict['lastUpdate'] > current_app_dict['settings']['update_time'])):
manifest = _fetch_app_from_git(app_id) manifest = _fetch_app_from_git(app_instance_name)
else: else:
continue continue
# Check requirements # Check requirements
_check_manifest_requirements(manifest) _check_manifest_requirements(manifest)
app_setting_path = apps_setting_path +'/'+ app_id app_setting_path = apps_setting_path +'/'+ app_instance_name
# Retrieve current app status # Retrieve current app status
status = _get_app_status(app_id) status = _get_app_status(app_instance_name)
status['remote'] = manifest.get('remote', None) status['remote'] = manifest.get('remote', None)
# Clean hooks and add new ones # Clean hooks and add new ones
hook_remove(app_id) hook_remove(app_instance_name)
if 'hooks' in os.listdir(app_tmp_folder): if 'hooks' in os.listdir(app_tmp_folder):
for hook in os.listdir(app_tmp_folder +'/hooks'): for hook in os.listdir(app_tmp_folder +'/hooks'):
hook_add(app_id, app_tmp_folder +'/hooks/'+ hook) hook_add(app_instance_name, app_tmp_folder +'/hooks/'+ hook)
# Retrieve arguments list for upgrade script # Retrieve arguments list for upgrade script
# TODO: Allow to specify arguments # TODO: Allow to specify arguments
args_list = _parse_args_from_manifest(manifest, 'upgrade', auth=auth) args_odict = _parse_args_from_manifest(manifest, 'upgrade', auth=auth)
args_list.append(app_id) args_list = args_odict.values()
args_list.append(app_instance_name)
# Prepare env. var. to pass to script
env_dict = _make_environment_dict(args_odict)
app_id, app_instance_nb = _parse_app_instance_name(app_instance_name)
env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
# Execute App upgrade script # Execute App upgrade script
os.system('chown -hR admin: %s' % install_tmp) os.system('chown -hR admin: %s' % install_tmp)
if hook_exec(app_tmp_folder +'/scripts/upgrade', args_list) != 0: if hook_exec(app_tmp_folder +'/scripts/upgrade', args=args_list, env=env_dict) != 0:
logger.error(m18n.n('app_upgrade_failed', app=app_id)) logger.error(m18n.n('app_upgrade_failed', app=app_instance_name))
else: else:
now = int(time.time()) now = int(time.time())
# TODO: Move install_time away from app_setting # TODO: Move install_time away from app_setting
app_setting(app_id, 'update_time', now) app_setting(app_instance_name, 'update_time', now)
status['upgraded_at'] = now status['upgraded_at'] = now
# Store app status # Store app status
@ -411,8 +424,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
os.system('mv "%s/manifest.json" "%s/scripts" %s' % (app_tmp_folder, app_tmp_folder, app_setting_path)) os.system('mv "%s/manifest.json" "%s/scripts" %s' % (app_tmp_folder, app_tmp_folder, app_setting_path))
# So much win # So much win
upgraded_apps.append(app_id) upgraded_apps.append(app_instance_name)
logger.success(m18n.n('app_upgraded', app=app_id)) logger.success(m18n.n('app_upgraded', app=app_instance_name))
if not upgraded_apps: if not upgraded_apps:
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade')) raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
@ -471,34 +484,43 @@ def app_install(auth, app, label=None, args=None):
m18n.n('app_already_installed', app=app_id)) m18n.n('app_already_installed', app=app_id))
# Change app_id to the forked app id # Change app_id to the forked app id
app_id = app_id + '__' + str(instance_number) app_instance_name = app_id + '__' + str(instance_number)
else:
app_instance_name = app_id
# Retrieve arguments list for install script # Retrieve arguments list for install script
args_dict = {} if not args else \ args_dict = {} if not args else \
dict(urlparse.parse_qsl(args, keep_blank_values=True)) dict(urlparse.parse_qsl(args, keep_blank_values=True))
args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth) args_odict = _parse_args_from_manifest(manifest, 'install', args=args_dict, auth=auth)
args_list.append(app_id) args_list = args_odict.values()
args_list.append(app_instance_name)
# Prepare env. var. to pass to script
env_dict = _make_environment_dict(args_odict)
env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
# Create app directory # Create app directory
app_setting_path = os.path.join(apps_setting_path, app_id) app_setting_path = os.path.join(apps_setting_path, app_instance_name)
if os.path.exists(app_setting_path): if os.path.exists(app_setting_path):
shutil.rmtree(app_setting_path) shutil.rmtree(app_setting_path)
os.makedirs(app_setting_path) os.makedirs(app_setting_path)
# Clean hooks and add new ones # Clean hooks and add new ones
hook_remove(app_id) hook_remove(app_instance_name)
if 'hooks' in os.listdir(app_tmp_folder): if 'hooks' in os.listdir(app_tmp_folder):
for file in os.listdir(app_tmp_folder +'/hooks'): for file in os.listdir(app_tmp_folder +'/hooks'):
hook_add(app_id, app_tmp_folder +'/hooks/'+ file) hook_add(app_instance_name, app_tmp_folder +'/hooks/'+ file)
# Set initial app settings # Set initial app settings
app_settings = { app_settings = {
'id': app_id, 'id': app_instance_name,
'label': label if label else manifest['name'], 'label': label if label else manifest['name'],
} }
# TODO: Move install_time away from app settings # TODO: Move install_time away from app settings
app_settings['install_time'] = status['installed_at'] app_settings['install_time'] = status['installed_at']
_set_app_settings(app_id, app_settings) _set_app_settings(app_instance_name, app_settings)
os.system('chown -R admin: '+ app_tmp_folder) os.system('chown -R admin: '+ app_tmp_folder)
@ -512,21 +534,30 @@ def app_install(auth, app, label=None, args=None):
install_retcode = 1 install_retcode = 1
try: try:
install_retcode = hook_exec( install_retcode = hook_exec(
os.path.join(app_tmp_folder, 'scripts/install'), args_list) os.path.join(app_tmp_folder, 'scripts/install'),
args=args_list, env=env_dict)
except (KeyboardInterrupt, EOFError): except (KeyboardInterrupt, EOFError):
install_retcode = -1 install_retcode = -1
except: except:
logger.exception(m18n.n('unexpected_error')) logger.exception(m18n.n('unexpected_error'))
finally: finally:
if install_retcode != 0: if install_retcode != 0:
# Setup environment for remove script
env_dict_remove = {}
env_dict_remove["YNH_APP_ID"] = app_id
env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
# Execute remove script # Execute remove script
remove_retcode = hook_exec( remove_retcode = hook_exec(
os.path.join(app_tmp_folder, 'scripts/remove'), [app_id]) os.path.join(app_tmp_folder, 'scripts/remove'),
args=[app_instance_name], env=env_dict_remove)
if remove_retcode != 0: if remove_retcode != 0:
logger.warning(m18n.n('app_not_properly_removed', app=app_id)) logger.warning(m18n.n('app_not_properly_removed',
app=app_instance_name))
# Clean tmp folders # Clean tmp folders
hook_remove(app_id) hook_remove(app_instance_name)
shutil.rmtree(app_setting_path) shutil.rmtree(app_setting_path)
shutil.rmtree(app_tmp_folder) shutil.rmtree(app_tmp_folder)
@ -1431,8 +1462,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None):
Retrieve specified arguments for the action from the manifest, and parse Retrieve specified arguments for the action from the manifest, and parse
given args according to that. If some required arguments are not provided, given args according to that. If some required arguments are not provided,
its values will be asked if interaction is possible. its values will be asked if interaction is possible.
Parsed arguments will be returned as a list of strings to pass directly Parsed arguments will be returned as an OrderedDict
to the proper script.
Keyword arguments: Keyword arguments:
manifest -- The app manifest to use manifest -- The app manifest to use
@ -1443,7 +1473,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None):
from yunohost.domain import domain_list from yunohost.domain import domain_list
from yunohost.user import user_info from yunohost.user import user_info
args_list = [] args_list = OrderedDict()
try: try:
action_args = manifest['arguments'][action] action_args = manifest['arguments'][action]
except KeyError: except KeyError:
@ -1531,9 +1561,49 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None):
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid', m18n.n('app_argument_choice_invalid',
name=arg_name, choices='0, 1')) name=arg_name, choices='0, 1'))
args_list.append(arg_value) args_list[arg_name] = arg_value
return args_list return args_list
def _make_environment_dict(args_dict):
"""
Convert a dictionnary containing manifest arguments
to a dictionnary of env. var. to be passed to scripts
Keyword arguments:
arg -- A key/value dictionnary of manifest arguments
"""
env_dict = {}
for arg_name, arg_value in args_dict.items():
env_dict[ "YNH_APP_ARG_%s" % arg_name.upper() ] = arg_value
return env_dict
def _parse_app_instance_name(app_instance_name):
"""
Parse a Yunohost app instance name and extracts the original appid
and the application instance number
>>> _parse_app_instance_name('yolo') == ('yolo', 1)
True
>>> _parse_app_instance_name('yolo1') == ('yolo1', 1)
True
>>> _parse_app_instance_name('yolo__0') == ('yolo__0', 1)
True
>>> _parse_app_instance_name('yolo__1') == ('yolo', 1)
True
>>> _parse_app_instance_name('yolo__23') == ('yolo', 23)
True
>>> _parse_app_instance_name('yolo__42__72') == ('yolo__42', 72)
True
>>> _parse_app_instance_name('yolo__23qdqsd') == ('yolo__23qdqsd', 1)
True
>>> _parse_app_instance_name('yolo__23qdqsd56') == ('yolo__23qdqsd56', 1)
True
"""
match = re_app_instance_name.match(app_instance_name)
appid = match.groupdict().get('appid')
app_instance_nb = int(match.groupdict().get('appinstancenb')) if match.groupdict().get('appinstancenb') is not None else 1
return (appid, app_instance_nb)
def is_true(arg): def is_true(arg):
""" """

View file

@ -39,7 +39,9 @@ from moulinette.core import MoulinetteError
from moulinette.utils import filesystem from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from yunohost.app import app_info, app_ssowatconf, _is_installed from yunohost.app import (
app_info, app_ssowatconf, _is_installed, _parse_app_instance_name
)
from yunohost.hook import ( from yunohost.hook import (
hook_info, hook_callback, hook_exec, custom_hook_folder hook_info, hook_callback, hook_exec, custom_hook_folder
) )
@ -190,21 +192,21 @@ def backup_create(name=None, description=None, output_directory=None,
# Run apps backup scripts # Run apps backup scripts
tmp_script = '/tmp/backup_' + str(timestamp) tmp_script = '/tmp/backup_' + str(timestamp)
for app_id in apps_filtered: for app_instance_name in apps_filtered:
app_setting_path = '/etc/yunohost/apps/' + app_id app_setting_path = '/etc/yunohost/apps/' + app_instance_name
# Check if the app has a backup and restore script # Check if the app has a backup and restore script
app_script = app_setting_path + '/scripts/backup' app_script = app_setting_path + '/scripts/backup'
app_restore_script = app_setting_path + '/scripts/restore' app_restore_script = app_setting_path + '/scripts/restore'
if not os.path.isfile(app_script): if not os.path.isfile(app_script):
logger.warning(m18n.n('unbackup_app', app=app_id)) logger.warning(m18n.n('unbackup_app', app=app_instance_name))
continue continue
elif not os.path.isfile(app_restore_script): elif not os.path.isfile(app_restore_script):
logger.warning(m18n.n('unrestore_app', app=app_id)) logger.warning(m18n.n('unrestore_app', app=app_instance_name))
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
tmp_app_bkp_dir = tmp_app_dir + '/backup' tmp_app_bkp_dir = tmp_app_dir + '/backup'
logger.info(m18n.n('backup_running_app_script', app=app_id)) logger.info(m18n.n('backup_running_app_script', app=app_instance_name))
try: try:
# Prepare backup directory for the app # Prepare backup directory for the app
filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin') filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin')
@ -212,16 +214,25 @@ def backup_create(name=None, description=None, output_directory=None,
# Copy app backup script in a temporary folder and execute it # Copy app backup script in a temporary folder and execute it
subprocess.call(['install', '-Dm555', app_script, tmp_script]) subprocess.call(['install', '-Dm555', app_script, tmp_script])
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id],
raise_on_error=True, chdir=tmp_app_bkp_dir) # Prepare env. var. to pass to script
env_dict = {}
app_id, app_instance_nb = _parse_app_instance_name(app_instance_name)
env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name],
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
except: except:
logger.exception(m18n.n('backup_app_failed', app=app_id)) logger.exception(m18n.n('backup_app_failed', app=app_instance_name))
# Cleaning app backup directory # Cleaning app backup directory
shutil.rmtree(tmp_app_dir, ignore_errors=True) shutil.rmtree(tmp_app_dir, ignore_errors=True)
else: else:
# Add app info # Add app info
i = app_info(app_id) i = app_info(app_instance_name)
info['apps'][app_id] = { info['apps'][app_instance_name] = {
'version': i['version'], 'version': i['version'],
'name': i['name'], 'name': i['name'],
'description': i['description'], 'description': i['description'],
@ -444,25 +455,25 @@ def backup_restore(auth, name, hooks=[], ignore_hooks=False,
else: else:
apps_filtered = apps_list apps_filtered = apps_list
for app_id in apps_filtered: for app_instance_name in apps_filtered:
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
tmp_app_bkp_dir = tmp_app_dir + '/backup' tmp_app_bkp_dir = tmp_app_dir + '/backup'
# Check if the app is not already installed # Check if the app is not already installed
if _is_installed(app_id): if _is_installed(app_instance_name):
logger.error(m18n.n('restore_already_installed_app', logger.error(m18n.n('restore_already_installed_app',
app=app_id)) app=app_instance_name))
continue continue
# Check if the app has a restore script # Check if the app has a restore script
app_script = tmp_app_dir + '/settings/scripts/restore' app_script = tmp_app_dir + '/settings/scripts/restore'
if not os.path.isfile(app_script): if not os.path.isfile(app_script):
logger.warning(m18n.n('unrestore_app', app=app_id)) logger.warning(m18n.n('unrestore_app', app=app_instance_name))
continue continue
tmp_script = '/tmp/restore_' + app_id tmp_script = '/tmp/restore_' + app_instance_name
app_setting_path = '/etc/yunohost/apps/' + app_id app_setting_path = '/etc/yunohost/apps/' + app_instance_name
logger.info(m18n.n('restore_running_app_script', app=app_id)) logger.info(m18n.n('restore_running_app_script', app=app_instance_name))
try: try:
# Copy app settings and set permissions # Copy app settings and set permissions
shutil.copytree(tmp_app_dir + '/settings', app_setting_path) shutil.copytree(tmp_app_dir + '/settings', app_setting_path)
@ -471,14 +482,23 @@ def backup_restore(auth, name, hooks=[], ignore_hooks=False,
# Execute app restore script # Execute app restore script
subprocess.call(['install', '-Dm555', app_script, tmp_script]) subprocess.call(['install', '-Dm555', app_script, tmp_script])
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id],
raise_on_error=True, chdir=tmp_app_bkp_dir) # Prepare env. var. to pass to script
env_dict = {}
app_id, app_instance_nb = _parse_app_instance_name(app_instance_name)
env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name],
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
except: except:
logger.exception(m18n.n('restore_app_failed', app=app_id)) logger.exception(m18n.n('restore_app_failed', app=app_instance_name))
# Cleaning app directory # Cleaning app directory
shutil.rmtree(app_setting_path, ignore_errors=True) shutil.rmtree(app_setting_path, ignore_errors=True)
else: else:
result['apps'].append(app_id) result['apps'].append(app_instance_name)
finally: finally:
filesystem.rm(tmp_script, force=True) filesystem.rm(tmp_script, force=True)

View file

@ -330,7 +330,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
cmd_script = path cmd_script = path
envcli = '' envcli = ''
if env is not None and isinstance(env, dict): if env is not None:
envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()]) envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()])
# Construct command to execute # Construct command to execute