[enh] Allow applications to ship a script to change its url (#185)

* [enh] add app_change_url
* [fix] avoid unecessary io and reuse already existing variable
* [fix] bad comment
* [fix] another bad comment
* [fix] I need to be able to call yunohost during change_url scripts
* [mod] global variables are now uppercased
* [mod] compress condition
* [enh] don't change_url if old/new domain_path are identical
* [mod] i18n
* [enh] ensure that nginx doesn't failed at the end of change_url
* [fix] forgot to call this damn m18n
* [mod] m18n
* [enh] ask and requires new domain/path for change_url
* [fix] missing translation key
* [mod] ordering
* [mod] lisibility
* [enh] avoid common mistakes
* [fix] check_output is annoying
* [fix] locale: typo.
* Adding changeurl unit test draft
* [mod] remove useless imports
* [mod] style
* [mod] change_url -> changeurl
* Moving comment about checkurl near checkurl call
* Normalize new path and domain format
* Adding test about trying to changeurl to same url
* Internationalizing change app success message
* Removing 'trimed' stuff
* Moving check for change_url script at beginning of function
* Use _run_service_command to reload nginx
* Changing changeurl back to change-url, gomennasai :s
This commit is contained in:
Laurent Peuch 2017-04-18 00:38:40 +02:00 committed by Alexandre Aubin
parent 7718ed6000
commit 1516f48699
4 changed files with 191 additions and 1 deletions

View file

@ -544,6 +544,31 @@ app:
full: --file full: --file
help: Folder or tarball for upgrade help: Folder or tarball for upgrade
### app_change_url()
change-url:
action_help: Change app's URL
api: PUT /apps/<app>/changeurl
configuration:
authenticate: all
authenticator: ldap-anonymous
lock: false
arguments:
app:
help: Target app instance name
-d:
full: --domain
help: New app domain on which the application will be moved
extra:
ask: ask_main_domain
pattern: *pattern_domain
required: True
-p:
full: --path
help: New path at which the application will be moved
extra:
ask: ask_path
required: True
### app_setting() ### app_setting()
setting: setting:
action_help: Set or get an app setting value action_help: Set or get an app setting value

View file

@ -8,6 +8,11 @@
"app_argument_choice_invalid": "Invalid choice for argument '{name:s}', it must be one of {choices:s}", "app_argument_choice_invalid": "Invalid choice for argument '{name:s}', it must be one of {choices:s}",
"app_argument_invalid": "Invalid value for argument '{name:s}': {error:s}", "app_argument_invalid": "Invalid value for argument '{name:s}': {error:s}",
"app_argument_required": "Argument '{name:s}' is required", "app_argument_required": "Argument '{name:s}' is required",
"app_change_no_change_url_script": "The application {app_name:s} doesn't support changing it's URL yet, you might need to upgrade it.",
"app_change_url_failed_nginx_reload": "Failed to reload nginx. Here is the output of 'nginx -t':\n{nginx_errors:s}",
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
"app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.",
"app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}",
"app_extraction_failed": "Unable to extract installation files", "app_extraction_failed": "Unable to extract installation files",
"app_id_invalid": "Invalid app id", "app_id_invalid": "Invalid app id",
"app_incompatible": "The app is incompatible with your YunoHost version", "app_incompatible": "The app is incompatible with your YunoHost version",

View file

@ -41,7 +41,7 @@ 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
from yunohost.service import service_log from yunohost.service import service_log, _run_service_command
from yunohost.utils import packages from yunohost.utils import packages
logger = getActionLogger('yunohost.app') logger = getActionLogger('yunohost.app')
@ -419,6 +419,105 @@ def app_map(app=None, raw=False, user=None):
return result return result
def app_change_url(auth, app, domain, path):
"""
Modify the URL at which an application is installed.
Keyword argument:
app -- Taget app instance name
domain -- New app domain on which the application will be moved
path -- New path at which the application will be move
"""
from yunohost.hook import hook_exec
installed = _is_installed(app)
if not installed:
raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app))
if not os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")):
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_no_change_url_script", app_name=app))
old_domain = app_setting(app, "domain")
old_path = app_setting(app, "path")
# Normalize path and domain format
domain = domain.strip().lower()
old_path = '/' + old_path.strip("/").strip() + '/'
path = '/' + path.strip("/").strip() + '/'
if (domain, path) == (old_domain, old_path):
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path))
# WARNING / FIXME : checkurl will modify the settings
# (this is a non intuitive behavior that should be changed)
# (or checkurl renamed in reserve_url)
app_checkurl(auth, '%s%s' % (domain, path), app)
manifest = json.load(open(os.path.join(APPS_SETTING_PATH, app, "manifest.json")))
# Retrieve arguments list for change_url script
# TODO: Allow to specify arguments
args_odict = _parse_args_from_manifest(manifest, 'change_url', auth=auth)
args_list = args_odict.values()
args_list.append(app)
# Prepare env. var. to pass to script
env_dict = _make_environment_dict(args_odict)
app_id, app_instance_nb = _parse_app_instance_name(app)
env_dict["YNH_APP_ID"] = app_id
env_dict["YNH_APP_INSTANCE_NAME"] = app
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
env_dict["YNH_APP_OLD_DOMAIN"] = old_domain
env_dict["YNH_APP_OLD_PATH"] = old_path.rstrip("/")
env_dict["YNH_APP_NEW_DOMAIN"] = domain
env_dict["YNH_APP_NEW_PATH"] = path.rstrip("/")
if os.path.exists(os.path.join(APP_TMP_FOLDER, "scripts")):
shutil.rmtree(os.path.join(APP_TMP_FOLDER, "scripts"))
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "scripts"),
os.path.join(APP_TMP_FOLDER, "scripts"))
# Execute App change_url script
os.system('chown -R admin: %s' % INSTALL_TMP)
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
# XXX journal
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'), args=args_list, env=env_dict) != 0:
logger.error("Failed to change '%s' url." % app)
# restore values modified by app_checkurl
# see begining of the function
app_setting(app, "domain", value=old_domain)
app_setting(app, "path", value=old_path)
return
# this should idealy be done in the change_url script but let's avoid common mistakes
app_setting(app, 'domain', value=domain)
app_setting(app, 'path', value=path)
app_ssowatconf(auth)
# avoid common mistakes
if _run_service_command("reload", "nginx") == False:
# grab nginx errors
# the "exit 0" is here to avoid check_output to fail because 'nginx -t'
# will return != 0 since we are in a failed state
nginx_errors = subprocess.check_output("nginx -t; exit 0",
stderr=subprocess.STDOUT,
shell=True).rstrip()
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_failed_nginx_reload", nginx_errors=nginx_errors))
logger.success(m18n.n("app_change_url_success",
app=app, domain=domain, path=path))
def app_upgrade(auth, app=[], url=None, file=None): def app_upgrade(auth, app=[], url=None, file=None):
""" """
Upgrade app Upgrade app

View file

@ -0,0 +1,61 @@
import pytest
import time
import requests
from moulinette.core import init_authenticator
from yunohost.app import app_install, app_change_url, app_remove, app_map
from yunohost.domain import _get_maindomain
from moulinette.core import MoulinetteError
# Instantiate LDAP Authenticator
AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous')
AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'}
auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS)
# Get main domain
maindomain = _get_maindomain()
def setup_function(function):
pass
def teardown_function(function):
app_remove(auth, "change_url_app")
def install_changeurl_app(path):
app_install(auth, "./tests/apps/change_url_app_ynh",
args="domain=%s&path=%s" % (maindomain, path))
def check_changeurl_app(path):
appmap = app_map(raw=True)
assert path + "/" in appmap[maindomain].keys()
assert appmap[maindomain][path + "/"]["id"] == "change_url_app"
r = requests.get("https://%s%s/" % (maindomain, path))
assert r.status_code == 200
def test_appchangeurl():
install_changeurl_app("/changeurl")
check_changeurl_app("/changeurl")
app_change_url(auth, "change_url_app", maindomain, "/newchangeurl")
# For some reason the nginx reload can take some time to propagate ...?
time.sleep(2)
check_changeurl_app("/newchangeurl")
def test_appchangeurl_sameurl():
install_changeurl_app("/changeurl")
check_changeurl_app("/changeurl")
with pytest.raises(MoulinetteError):
app_change_url(auth, "change_url_app", maindomain, "changeurl")