Merge remote-tracking branch 'upstream/unstable' into unstable

Conflicts:
	data/actionsmap/yunohost.yml
This commit is contained in:
zamentur 2015-10-02 02:57:21 +02:00
commit 7ef3b7523b
3 changed files with 172 additions and 39 deletions

View file

@ -358,11 +358,15 @@ app:
### app_info() ### app_info()
info: info:
action_help: Get app info action_help: Get information about an installed app
api: GET /apps/<app> api: GET /apps/<app>
arguments: arguments:
app: app:
help: Specific app ID help: Specific app ID
-s:
full: --show-status
help: Show app installation status
action: store_true
-r: -r:
full: --raw full: --raw
help: Return the full app_dict help: Return the full app_dict
@ -601,8 +605,6 @@ backup:
restore: restore:
action_help: Restore from a local backup archive action_help: Restore from a local backup archive
api: POST /backup/restore/<name> api: POST /backup/restore/<name>
configuration:
authenticate: false
arguments: arguments:
name: name:
help: Name of the local backup archive help: Name of the local backup archive

2
debian/control vendored
View file

@ -13,7 +13,7 @@ Depends: moulinette (>= 2.2.1),
python-requests, python-requests,
glances, glances,
python-pip, python-pip,
pyminiupnpc, python-miniupnpc | pyminiupnpc,
dnsutils, dnsutils,
bind9utils, bind9utils,
python-apt, python-apt,

View file

@ -34,8 +34,12 @@ import re
import socket import socket
import urlparse import urlparse
import errno import errno
import subprocess
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
logger = getActionLogger('yunohost.app')
repo_path = '/var/cache/yunohost/repo' repo_path = '/var/cache/yunohost/repo'
apps_path = '/usr/share/yunohost/apps' apps_path = '/usr/share/yunohost/apps'
@ -169,6 +173,8 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
if raw: if raw:
app_info['installed'] = installed app_info['installed'] = installed
if installed:
app_info['status'] = _get_app_status(app_id)
list_dict[app_id] = app_info list_dict[app_id] = app_info
else: else:
list_dict.append({ list_dict.append({
@ -189,37 +195,44 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
return list_dict return list_dict
def app_info(app, raw=False): def app_info(app, show_status=False, raw=False):
""" """
Get app info Get app info
Keyword argument: Keyword argument:
app -- Specific app ID app -- Specific app ID
show_status -- Show app installation status
raw -- Return the full app_dict raw -- Return the full app_dict
""" """
try: if not _is_installed(app):
app_info = app_list(filter=app, raw=True)[app] raise MoulinetteError(errno.EINVAL,
except: m18n.n('app_not_installed', app))
app_info = {} if raw:
ret = app_list(filter=app, raw=True)[app]
if _is_installed(app):
with open(apps_setting_path + app +'/settings.yml') as f: with open(apps_setting_path + app +'/settings.yml') as f:
app_info['settings'] = yaml.load(f) ret['settings'] = yaml.load(f)
return ret
if raw: app_setting_path = apps_setting_path + app
return app_info
else: # Retrieve manifest and status
return { with open(app_setting_path + '/manifest.json') as f:
'name': app_info['manifest']['name'], manifest = json.loads(str(f.read()))
'description': _value_for_locale(app_info['manifest']['description']), status = _get_app_status(app, format_date=True)
# FIXME: Temporarly allow undefined license
'license': app_info['manifest'].get('license', info = {
m18n.n('license_undefined')), 'name': manifest['name'],
# FIXME: Temporarly allow undefined version 'description': _value_for_locale(manifest['description']),
'version' : app_info['manifest'].get('version', '-'), # FIXME: Temporarly allow undefined license
#TODO: Add more info 'license': manifest.get('license', m18n.n('license_undefined')),
} # FIXME: Temporarly allow undefined version
'version': manifest.get('version', '-'),
#TODO: Add more info
}
if show_status:
info['status'] = status
return info
def app_map(app=None, raw=False, user=None): def app_map(app=None, raw=False, user=None):
@ -274,7 +287,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
url -- Git url to fetch for upgrade url -- Git url to fetch for upgrade
""" """
from yunohost.hook import hook_add, hook_exec from yunohost.hook import hook_add, hook_remove, hook_exec
try: try:
app_list() app_list()
@ -330,6 +343,10 @@ def app_upgrade(auth, app=[], url=None, file=None):
app_setting_path = apps_setting_path +'/'+ app_id app_setting_path = apps_setting_path +'/'+ app_id
# Retrieve current app status
status = _get_app_status(app_id)
status['remote'] = manifest.get('remote', None)
if original_app_id != app_id: if original_app_id != app_id:
# Replace original_app_id with the forked one in scripts # Replace original_app_id with the forked one in scripts
for script in os.listdir(app_tmp_folder +'/scripts'): for script in os.listdir(app_tmp_folder +'/scripts'):
@ -362,7 +379,14 @@ def app_upgrade(auth, app=[], url=None, file=None):
if hook_exec(app_tmp_folder +'/scripts/upgrade') != 0: if hook_exec(app_tmp_folder +'/scripts/upgrade') != 0:
raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) raise MoulinetteError(errno.EIO, m18n.n('installation_failed'))
else: else:
app_setting(app_id, 'update_time', int(time.time())) now = int(time.time())
# TODO: Move install_time away from app_setting
app_setting(app_id, 'update_time', now)
status['upgraded_at'] = now
# Store app status
with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
# Replace scripts and manifest # Replace scripts and manifest
os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path)) os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path))
@ -394,12 +418,21 @@ def app_install(auth, app, label=None, args=None):
try: os.listdir(install_tmp) try: os.listdir(install_tmp)
except OSError: os.makedirs(install_tmp) except OSError: os.makedirs(install_tmp)
status = {
'installed_at': None,
'upgraded_at': None,
'remote': {
'type': None,
},
}
if app in app_list(raw=True) or ('@' in app) or ('http://' in app) or ('https://' in app): if app in app_list(raw=True) or ('@' in app) or ('http://' in app) or ('https://' in app):
manifest = _fetch_app_from_git(app) manifest = _fetch_app_from_git(app)
elif os.path.exists(app): elif os.path.exists(app):
manifest = _extract_app_from_file(app) manifest = _extract_app_from_file(app)
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
status['remote'] = manifest.get('remote', {})
# Check ID # Check ID
if 'id' not in manifest or '__' in manifest['id']: if 'id' not in manifest or '__' in manifest['id']:
@ -458,8 +491,11 @@ def app_install(auth, app, label=None, args=None):
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_id, app_tmp_folder +'/hooks/'+ file)
now = int(time.time())
app_setting(app_id, 'id', app_id) app_setting(app_id, 'id', app_id)
app_setting(app_id, 'install_time', int(time.time())) # TODO: Move install_time away from app_setting
app_setting(app_id, 'install_time', now)
status['installed_at'] = now
if label: if label:
app_setting(app_id, 'label', label) app_setting(app_id, 'label', label)
@ -482,6 +518,11 @@ def app_install(auth, app, label=None, args=None):
os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path))
try: try:
if hook_exec(app_tmp_folder + '/scripts/install', args_dict) == 0: if hook_exec(app_tmp_folder + '/scripts/install', args_dict) == 0:
# Store app status
with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
# Clean and set permissions
shutil.rmtree(app_tmp_folder) shutil.rmtree(app_tmp_folder)
os.system('chmod -R 400 %s' % app_setting_path) os.system('chmod -R 400 %s' % app_setting_path)
os.system('chown -R root: %s' % app_setting_path) os.system('chown -R root: %s' % app_setting_path)
@ -750,9 +791,11 @@ def app_setting(app, key, value=None, delete=False):
app_settings = {} app_settings = {}
if value is None and not delete: if value is None and not delete:
# Get the value try:
if app_settings is not None and key in app_settings:
return app_settings[key] return app_settings[key]
except:
logger.exception("cannot get app setting '%s' for '%s'", key, app)
return None
else: else:
yaml_settings=['redirected_urls','redirected_regex'] yaml_settings=['redirected_urls','redirected_regex']
# Set the value # Set the value
@ -959,6 +1002,45 @@ def app_ssowatconf(auth):
msignals.display(m18n.n('ssowat_conf_generated'), 'success') msignals.display(m18n.n('ssowat_conf_generated'), 'success')
def _get_app_status(app_id, format_date=False):
"""
Get app status or create it if needed
Keyword arguments:
app_id -- The app id
format_date -- Format date fields
"""
app_setting_path = apps_setting_path + app_id
if not os.path.isdir(app_setting_path):
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
status = {}
try:
with open(app_setting_path + '/status.json') as f:
status = json.loads(str(f.read()))
except IOError:
logger.exception("status file not found for '%s'", app_id)
# Create app status
status = {
'installed_at': app_setting(app_id, 'install_time'),
'upgraded_at': app_setting(app_id, 'update_time'),
'remote': { 'type': None },
}
with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
if format_date:
for f in ['installed_at', 'upgraded_at']:
v = status.get(f, None)
if not v:
status[f] = '-'
else:
status[f] = time.strftime(m18n.n('format_datetime_short'),
time.gmtime(v))
return status
def _extract_app_from_file(path, remove=False): def _extract_app_from_file(path, remove=False):
""" """
Unzip or untar application tarball in app_tmp_folder, or copy it from a directory Unzip or untar application tarball in app_tmp_folder, or copy it from a directory
@ -1009,9 +1091,30 @@ def _extract_app_from_file(path, remove=False):
msignals.display(m18n.n('done')) msignals.display(m18n.n('done'))
manifest['remote'] = {'type': 'file', 'path': path}
return manifest return manifest
def _get_git_last_commit_hash(repository):
"""
Attempt to retrieve the last commit hash of a git repository
Keyword arguments:
repository -- The URL or path of the repository
"""
try:
commit = subprocess.check_output(
"git ls-remote --exit-code {:s} HEAD | awk '{{print $1}}'".format(
repository),
shell=True)
except subprocess.CalledProcessError:
logger.exception("unable to get last commit from %s", repository)
raise ValueError("Unable to get last commit with git")
else:
return commit.strip()
def _fetch_app_from_git(app): def _fetch_app_from_git(app):
""" """
Unzip or untar application tarball in app_tmp_folder Unzip or untar application tarball in app_tmp_folder
@ -1027,6 +1130,8 @@ def _fetch_app_from_git(app):
msignals.display(m18n.n('downloading')) msignals.display(m18n.n('downloading'))
git_result_2 = 1
if ('@' in app) or ('http://' in app) or ('https://' in app): if ('@' in app) or ('http://' in app) or ('https://' in app):
if "github.com" in app: if "github.com" in app:
url = app.replace("git@github.com:", "https://github.com/") url = app.replace("git@github.com:", "https://github.com/")
@ -1034,16 +1139,28 @@ def _fetch_app_from_git(app):
if "/" in url [-1:]: url = url[:-1] if "/" in url [-1:]: url = url[:-1]
url = url + "/archive/master.zip" url = url + "/archive/master.zip"
if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0: if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0:
return _extract_app_from_file(app_tmp_folder +'.zip', remove=True) manifest = _extract_app_from_file(app_tmp_folder +'.zip', remove=True)
del manifest['remote']
else:
git_result = os.system('git clone --depth=1 %s %s' % (app, app_tmp_folder))
git_result_2 = 0
try:
with open(app_tmp_folder + '/manifest.json') as json_manifest:
manifest = json.loads(str(json_manifest.read()))
manifest['lastUpdate'] = int(time.time())
except IOError:
raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid'))
git_result = os.system('git clone --depth=1 %s %s' % (app, app_tmp_folder)) # Store remote repository info into the returned manifest
git_result_2 = 0 manifest['remote'] = {'type': 'git', 'url': app, 'branch': 'master'}
try: try:
with open(app_tmp_folder + '/manifest.json') as json_manifest: revision = _get_git_last_commit_hash(app)
manifest = json.loads(str(json_manifest.read())) except: pass
manifest['lastUpdate'] = int(time.time()) else:
except IOError: manifest['remote']['revision'] = revision
raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid'))
if git_result_2 == 1:
return manifest
else: else:
app_dict = app_list(raw=True) app_dict = app_list(raw=True)
@ -1056,12 +1173,26 @@ def _fetch_app_from_git(app):
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
if "github.com" in app_info['git']['url']: if "github.com" in app_info['git']['url']:
# FIXME: Retrieve branch defined in app_info
url = app_info['git']['url'].replace("git@github.com:", "https://github.com/") url = app_info['git']['url'].replace("git@github.com:", "https://github.com/")
if ".git" in url[-4:]: url = url[:-4] if ".git" in url[-4:]: url = url[:-4]
if "/" in url [-1:]: url = url[:-1] if "/" in url [-1:]: url = url[:-1]
url = url + "/archive/"+ str(app_info['git']['revision']) + ".zip" url = url + "/archive/"+ str(app_info['git']['revision']) + ".zip"
if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0: if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0:
return _extract_app_from_file(app_tmp_folder +'.zip', remove=True) manifest = _extract_app_from_file(app_tmp_folder +'.zip', remove=True)
else:
git_result_2 = 0
# Store remote repository info into the returned manifest
manifest['remote'] = {
'type': 'git',
'url': app_info['git']['url'],
'branch': app_info['git']['branch'],
'revision': app_info['git']['revision'],
}
if git_result_2 == 1:
return manifest
app_tmp_folder = install_tmp +'/'+ app app_tmp_folder = install_tmp +'/'+ app
if os.path.exists(app_tmp_folder): shutil.rmtree(app_tmp_folder) if os.path.exists(app_tmp_folder): shutil.rmtree(app_tmp_folder)