Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-tenant in simple mode #11596

Draft
wants to merge 1 commit into
base: 2.8
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 35 additions & 56 deletions bin/build-l10n
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import argparse
import glob
import os
import shutil
import subprocess


Expand All @@ -14,91 +13,71 @@ def main() -> None:
parser.add_argument("--dry-run", action="store_true", help="run in dry-run mode")
args = parser.parse_args()

all_suffix = [""] if args.suffix is None else args.suffix
all_suffix = [""] if args.suffix is None else ["", *args.suffix]

package_base_path = f"/tmp/config/geoportal/{args.package}_geoportal" # nosec
base_path = f"{package_base_path}/locale"
dest_package = "geomapfishapp" if os.environ.get("SIMPLE", "false").lower() == "true" else args.package

for lang in os.listdir(base_path):
for suffix in all_suffix:
if args.dry_run:
print(
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po => "
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.mo"
)
else:
subprocess.run(
[
"msgfmt",
"-o",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.mo",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po",
],
cwd=base_path,
check=True,
)

if os.path.exists(f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.mo"):
if os.path.exists(f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po"):
if args.dry_run:
print(
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.po => "
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.mo"
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po => "
f"{base_path}/{lang}/LC_MESSAGES/{dest_package}_geoportal-client{suffix}.mo"
)
else:
subprocess.run(
[
"msgfmt",
"-o",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.mo",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.po",
f"{lang}/LC_MESSAGES/{dest_package}_geoportal-client{suffix}.mo",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po",
],
cwd=base_path,
check=True,
)

if args.dry_run:
print(
f"/opt/c2cgeoportal/geoportal/c2cgeoportal_geoportal/locale/{lang}/LC_MESSAGES/ngeo.po, "
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po => "
f"{package_base_path}/static/{lang}{suffix}.json"
)
else:
with open(f"{package_base_path}/static/{lang}{suffix}.json", "w", encoding="utf-8") as out:
if os.path.exists(f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.mo"):
if args.dry_run:
print(
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.po => "
f"{base_path}/{lang}/LC_MESSAGES/{dest_package}_geoportal-server{suffix}.mo"
)
else:
subprocess.run(
[
"compile-catalog",
f"/opt/c2cgeoportal/geoportal/c2cgeoportal_geoportal/locale/{lang}/LC_MESSAGES/ngeo.po",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po",
"msgfmt",
"-o",
f"{lang}/LC_MESSAGES/{dest_package}_geoportal-server{suffix}.mo",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-server{suffix}.po",
],
stdout=out,
cwd=base_path,
check=True,
)

if os.environ.get("SIMPLE", "false").lower() == "true":
if args.suffix is not None:
print("ERROR: simple mod is not compatible with suffix")
if args.dry_run:
print(
f"mv {base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client.mo"
f"{base_path}/{lang}/LC_MESSAGES/geomapfishapp_geoportal-client.mo"
)
else:
shutil.move(
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client.mo",
f"{base_path}/{lang}/LC_MESSAGES/geomapfishapp_geoportal-client.mo",
)
if os.path.exists(f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server.mo"):
if os.path.exists(f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po"):
if args.dry_run:
print(
f"mv {base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server.mo"
f"{base_path}/{lang}/LC_MESSAGES/geomapfishapp_geoportal-server.mo"
f"/opt/c2cgeoportal/geoportal/c2cgeoportal_geoportal/locale/{lang}/LC_MESSAGES/ngeo.po, "
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po => "
f"{package_base_path}/static/{lang}{suffix}.json"
)
else:
shutil.move(
f"{base_path}/{lang}/LC_MESSAGES/{args.package}_geoportal-server.mo",
f"{base_path}/{lang}/LC_MESSAGES/geomapfishapp_geoportal-server.mo",
)
with open(
f"{package_base_path}/static/{lang}{suffix}.json", "w", encoding="utf-8"
) as out:
subprocess.run(
[
"compile-catalog",
f"/opt/c2cgeoportal/geoportal/c2cgeoportal_geoportal/locale/{lang}/LC_MESSAGES/ngeo.po",
f"{lang}/LC_MESSAGES/{args.package}_geoportal-client{suffix}.po",
],
stdout=out,
cwd=base_path,
check=True,
)

for po_file in glob.glob(f"{base_path}/*/LC_MESSAGES/*.po"):
if args.dry_run:
Expand Down
11 changes: 11 additions & 0 deletions bin/eval-templates
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,15 @@ find /etc/static-ngeo/ \( -name '*.js' -or -name '*.css' -or -name '*.html' \) -
sed --in-place --expression="s#\.__ENTRY_POINT__#${VISIBLE_ENTRY_POINT}#g" "${file}"
done

if [ -d /app/geomapfishapp_geoportal/ ]; then
for name in authentication multi_tenant dev; do
if [ -f "/etc/geomapfish/${name}.py" ]; then
echo "Get: ${name}.py"
cp "/etc/geomapfish/${name}.py" /app/geomapfishapp_geoportal/
fi
done
fi

chmod go-w -R /app/*_geoportal/

exec "$@"
4 changes: 2 additions & 2 deletions doc/integrator/create_application.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ We recommend instead that you use dynamic variables as described below.
However, in some use cases extending ``vars.yaml`` may be needed:

* Configuring highly specific environments
* Configuration of a multi-organization project
* Configuration of a multi-tenant project

Use of dynamic variables
........................
Expand Down Expand Up @@ -272,7 +272,7 @@ Do not forget to add your changes to git:

.. note::

If you are using a multi-organization project, you should add all new children to
If you are using a multi-tenant project, you should add all new children to
the parent site check_collector configuration.

After creation and minimal setup the application is ready to be installed.
Expand Down
2 changes: 1 addition & 1 deletion doc/integrator/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ Features that require additional steps (most of the time):
urllogin
pdfreport
routing
multi_organization
multi_tenant
vector_tiles
extend_application
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _integrator_multi_organization:
.. _integrator_multi_tenant:

Multi organization
==================
Multi-tenant
============

The geoportal can host multiple organizations, with configuration differences for each organization.
In a multi-organization geoportal, each organization will have the same program code
Expand All @@ -11,18 +11,21 @@ In this example we will have the came CSS but we can do some variations by using
see ``cssVars`` in ``gmfOptions`` in the ngeo GMF constants definitions
:ngeo_doc:`gmf constants </jsdoc/module-contribs_gmf_src_options.html>`.

The following lines will provide a basic implementation for multi-organization.
The following lines will provide a basic implementation for multi-tenant.

The code should be adapted, currently it handles the hostnames 'org1.camptocamp.com' and
'org2.camptocamp.com', and you probably want to put the hardcoded values in the config.

``__init__.py``
---------------
``multi_tenant.py``
-------------------

In the file ``geoportal/<package>_geoportal/__init__.py`` add the following lines:
You should have a ``geoportal/<package>_geoportal/multi_tenant.py`` file like this one:

.. code:: python

from pyramid.config import Configurator


def get_instance_prefix(request):
if request.host == "org1.camptocamp.com":
return "org1"
Expand All @@ -49,7 +52,9 @@ In the file ``geoportal/<package>_geoportal/__init__.py`` add the following line
return print_url


# In ``main`` function, after ``config.include("c2cgeoportal_geoportal")``
def includeme(config: Configurator) -> None:
"""Initialize the multi-tenant."""

config.add_request_method(
get_organization_role, name="get_organization_role")
config.add_request_method(
Expand Down Expand Up @@ -107,10 +112,22 @@ Internationalization

For each organization, a set of localization file should be created.

First you should create a ``tenants.yaml`` file like that:

.. code:: yaml

tenants:
org1:
public_url: https://org1.camptocamp.com
suffix: -org1
curl_args: <optional>
org2:
...

The general workflow is:

- The integrator performs ``make update-client-po``.
- This will run one ``update-po`` script for each organization with different environment variables.
- run ``scripts/multi-tenant-update-po``.
- This will run one ``make update-po-from-url`` for each organization with different environment variables.
- The result is one po file set for each organization with the defined suffix.
- The integrator needs to complete the po files with translations.
- When the config Docker image is built, all po files are automatically converted to JSON files
Expand Down
2 changes: 1 addition & 1 deletion doc/integrator/ngeo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The sub section is the interface name, and after that we have:
``Request.route_url`` `documentation
<https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.route_url>`_.

* ``lang_urls_suffix`` suffix used in l10n URL, see: :ref:`integrator_multi_organization`.
* ``lang_urls_suffix`` suffix used in l10n URL, see: :ref:`integrator_multi_tenant`.

The dynamic values names are:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ RUN --mount=type=cache,target=/root/.cache \
python3 -m pip install --disable-pip-version-check --editable=/app/ \
&& python3 -m compileall -q /usr/local/lib/python3.* \
-x '/(ptvsd|.*pydev.*|networkx)/' \
&& python3 -m compileall -q /app/{{cookiecutter.package}}_geoportal -x /app/{{cookiecutter.package}}_geoportal/static.*
&& python3 -m compileall -q "/app/{{cookiecutter.package}}_geoportal" -x "/app/{{cookiecutter.package}}_geoportal/static".* \
&& chmod go+w "/app/{{cookiecutter.package}}_geoportal" "/app/{{cookiecutter.package}}_geoportal/authentication.py" "/app/{{cookiecutter.package}}_geoportal/multi_tenant.py" "/app/{{cookiecutter.package}}_geoportal/dev.py"

ARG GIT_HASH
RUN c2cwsgiutils-genversion ${GIT_HASH}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import {{cookiecutter.package}}_geoportal.authentication
import {{cookiecutter.package}}_geoportal.dev
import {{cookiecutter.package}}_geoportal.multi_organization
import {{cookiecutter.package}}_geoportal.multi_tenant
from c2cgeoportal_geoportal import add_interface_config, locale_negotiator
from c2cgeoportal_geoportal.lib.i18n import LOCALE_PATH
from {{cookiecutter.package}}_geoportal.resources import Root
Expand All @@ -29,6 +30,7 @@ def main(global_config, **settings):
config.include("c2cgeoportal_geoportal")

config.include({{cookiecutter.package}}_geoportal.multi_organization.includeme)
config.include({{cookiecutter.package}}_geoportal.multi_tenant.includeme)

# Scan view decorator for adding routes
config.scan()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@


def includeme(config: Configurator) -> None:
"""Initialize the multi organization."""
"""Initialize the multi-tenant."""

del config # Unused
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pyramid.config import Configurator


def includeme(config: Configurator) -> None:
"""Initialize the multi-tenant."""

del config # Unused
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@
!geoportal/{{cookiecutter.package}}_geoportal/static
!geoportal/{{cookiecutter.package}}_geoportal/locale
geoportal/{{cookiecutter.package}}_geoportal/locale/*.pot
!geoportal/{{cookiecutter.package}}_geoportal/authentication.py
!geoportal/{{cookiecutter.package}}_geoportal/multi_tenant.py
!geoportal/{{cookiecutter.package}}_geoportal/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ENV PGSCHEMA=$PGSCHEMA

RUN \
cd /tmp/config/geoportal/ \
&& [ "${SIMPLE}" == "TRUE" ] || rm -f {{cookiecutter.package}}_geoportal/*.py \
&& c2c-template --vars ${VARS_FILE} \
--get-config {{cookiecutter.package}}_geoportal/config.yaml \
${CONFIG_VARS} \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PROJECT_PUBLIC_URL=https://example.camptocamp.com/
PROJECT_PUBLIC_URL?=https://example.camptocamp.com/
DUMP_FILE=dump.backup
PACKAGE={{cookiecutter.package}}
LANGUAGES=en fr de it
Expand All @@ -13,10 +13,10 @@ help: ## Display this help message

.PHONY: update-po-from-url
update-po-from-url: ## Update the po files from the URL provide by PROJECT_PUBLIC_URL
curl --fail --retry 5 --retry-delay 1 \
curl ${CURL_ARGS} --fail --retry 5 --retry-delay 1 \
$(PROJECT_PUBLIC_URL)locale.pot > geoportal/${PACKAGE}_geoportal/locale/${PACKAGE}_geoportal-client${SUFFIX}.pot
sed -i '/^"POT-Creation-Date: /d' geoportal/${PACKAGE}_geoportal/locale/${PACKAGE}_geoportal-client${SUFFIX}.pot
docker-compose run --rm -T tools update-po-only `id --user` `id --group` $(LANGUAGES)
docker-compose run --rm -T --env=SUFFIX=${SUFFIX} tools update-po-only `id --user` `id --group` $(LANGUAGES)

.PHONY: update-po
update-po: ## Update the po files from the running composition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ services:
- C2CWSGIUTILS_LOG_LEVEL
- LOG_TYPE
- C2CGEOPORTAL_THEME_TIMEOUT=300
# For multi tenant
- DEFAULT_PREFIX

geoportal-advance:
image: ${DOCKER_BASE}-geoportal:${DOCKER_TAG}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ C2C_AUTH_GITHUB_SCOPE=repo
#C2C_AUTH_GITHUB_SECRET=<secret>
#C2C_AUTH_GITHUB_PROXY_URL=https://geoservicies.camptocamp.com/redirect
C2C_USE_SESSION=true

# For multi-tenant
DEFAULT_PREFIX=unknown
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3

import argparse
import os
import subprocess

import yaml


def _main() -> None:
parser = argparse.ArgumentParser(
description="\n".join(
[
"Update the po files in multi tenants mode",
"",
"Using the information available in the tenants.yaml file.",
]
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.parse_args()

with open("tenants.yaml", "r") as tenants_file:
tenants = yaml.safe_load(tenants_file.read())

for name, tenant in tenants.get("tenants", {}).items():
print(f"Update localization for tenant {name}.")
subprocess.run(
[
"make",
"update-po-from-url",
],
check=True,
env={
**os.environ,
"PROJECT_PUBLIC_URL": tenant["public_url"],
"SUFFIX": tenant["suffix"],
"CURL_ARGS": tenant.get("curl_args", ""),
},
)


if __name__ == "__main__":
_main()
Loading