mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge remote-tracking branch 'upstream/unstable' into unstable
Conflicts: data/actionsmap/yunohost.yml
This commit is contained in:
commit
7ef3b7523b
3 changed files with 172 additions and 39 deletions
|
@ -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
2
debian/control
vendored
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue