diff --git a/.gitignore b/.gitignore index d18d5cc..04228fa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ __pycache__ secret.txt /local_test/ /poetry.lock +/coverage.xml +/htmlcov/ diff --git a/Makefile b/Makefile index e9ae7ae..01bfae7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ SHELL := /bin/bash +MAX_LINE_LENGTH := 119 all: help @@ -27,8 +28,18 @@ update: install-poetry ## update the sources and installation and generate "con poetry update poetry export -f requirements.txt --output conf/requirements.txt -local-test: check-poetry ## Run local_test.py to run the project locally - poetry run ./local_test.py +lint: ## Run code formatters and linter + poetry run flynt --fail-on-change --line_length=${MAX_LINE_LENGTH} . + poetry run isort --check-only . + poetry run flake8 . + +fix-code-style: ## Fix code formatting + poetry run flynt --line_length=${MAX_LINE_LENGTH} . + poetry run black --verbose --safe --line-length=${MAX_LINE_LENGTH} --skip-string-normalization . + poetry run isort . + +local-test: install ## Run local_test.py to run the project locally + poetry run python3 ./local_test.py local-diff-settings: ## Run "manage.py diffsettings" with local test poetry run python3 local_test/opt_yunohost/manage.py diffsettings diff --git a/README.md b/README.md index 410294a..83ffac6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Pull requests welcome ;) ## Settings and upgrades -Almost everything related to PyInventory's configuration is handled in a `"../conf/ynh_pyinventory_settings.py"` file. +Almost everything related to PyInventory's configuration is handled in a `"../conf/settings.py"` file. You can edit the file `$final_path/local_settings.py` to enable or disable features. # Miscellaneous @@ -100,13 +100,13 @@ drwxr-xr-x 3 root root 3 Dec 8 08:36 .. -rw-r--r-- 1 pyinventory pyinventory 171 Dec 8 08:39 secret.txt drwxr-xr-x 6 pyinventory pyinventory 6 Dec 8 08:37 venv -rw-r--r-- 1 pyinventory pyinventory 115 Dec 8 08:39 wsgi.py --rw-r--r-- 1 pyinventory pyinventory 4737 Dec 8 08:39 ynh_pyinventory_settings.py +-rw-r--r-- 1 pyinventory pyinventory 4737 Dec 8 08:39 settings.py root@yunohost:~# cd /opt/yunohost/pyinventory/ root@yunohost:/opt/yunohost/pyinventory# source venv/bin/activate (venv) root@yunohost:/opt/yunohost/pyinventory# ./manage.py check PyInventory v0.8.2 (Django v2.2.17) -DJANGO_SETTINGS_MODULE='ynh_pyinventory_settings' +DJANGO_SETTINGS_MODULE='settings' PROJECT_PATH:/opt/yunohost/pyinventory/venv/lib/python3.7/site-packages BASE_PATH:/opt/yunohost/pyinventory System check identified no issues (0 silenced). diff --git a/conf/create_superuser.py b/conf/create_superuser.py deleted file mode 100644 index 21cb858..0000000 --- a/conf/create_superuser.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import sys - - -def main(): - os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_pyinventory_settings' - - parser = argparse.ArgumentParser( - description='Create or update Django super user.' - ) - parser.add_argument('--username') - parser.add_argument('--email') - parser.add_argument('--password') - - args = parser.parse_args() - username = args.username - email = args.email or '' - password = args.password - - import django - django.setup() - - from django.contrib.auth import get_user_model - User = get_user_model() - user = User.objects.filter(username=username).first() - if user: - print('Update existing user and set his password.', file=sys.stderr) - user.is_active = True - user.is_staff = True - user.is_superuser = True - user.set_password(password) - user.email = email - user.save() - else: - print('Create new super user', file=sys.stderr) - User.objects.create_superuser( - username=username, - email=email, - password=password - ) - - -if __name__ == '__main__': - main() diff --git a/conf/gunicorn.conf.py b/conf/gunicorn.conf.py index fde7958..bcab967 100644 --- a/conf/gunicorn.conf.py +++ b/conf/gunicorn.conf.py @@ -3,6 +3,7 @@ """ import multiprocessing + bind = '127.0.0.1:__PORT__' # https://docs.gunicorn.org/en/latest/settings.html#workers diff --git a/conf/manage.py b/conf/manage.py index c0f3ae6..a85e3b1 100755 --- a/conf/manage.py +++ b/conf/manage.py @@ -7,6 +7,7 @@ import sys def main(): os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/conf/requirements.txt b/conf/requirements.txt index 19b9892..204e24c 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,7 +1,7 @@ bleach==3.2.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \ --hash=sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd \ --hash=sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080 -bx-py-utils==18; python_version >= "3.7" and python_full_version < "4.0.0" \ +bx-py-utils==18; python_version >= "3.6" and python_full_version < "4.0.0" \ --hash=sha256:72a6090822544603e3a7547ce07f0120ae67940ca2ec4590ac907b3b09ad70ca \ --hash=sha256:195ea1b3d5d277354ea33e34ec3ebd4fc2a6e8d94d646ede902f80527f06ec75 certifi==2020.12.5; python_version >= "3.7" and python_full_version < "3.0.0" or python_version >= "3.7" and python_full_version < "4.0.0" and python_full_version >= "3.5.0" \ @@ -61,9 +61,9 @@ django-tagulous==1.1.0; python_version >= "3.7" and python_full_version < "4.0.0 django-tools==0.48.3; python_version >= "3.7" and python_full_version < "4.0.0" \ --hash=sha256:08ed2ae606067f49c2c3949055227a826c8b880e5816114925eca386cf1823af \ --hash=sha256:40444fa16b703b7c6960a800ba76aad42472c9aa70040d549a4d91dbb47a5ddb -django-ynh==0.1.0; python_version >= "3.7" and python_full_version < "4.0.0" \ - --hash=sha256:db75d5d9f0b1744187168a1b815272cd8c86c9ecb0fd77c58517e82a2cd9b09b \ - --hash=sha256:955dc29f55c449e116876c7c920dc8451157bae2a246b1375822f5e47d4c6fd4 +django-ynh==0.1.2; python_version >= "3.7" and python_full_version < "4.0.0" \ + --hash=sha256:2efa30444f67252bbb7601e1b5631ce93ddf72a70b2216bb61363990de78ad4f \ + --hash=sha256:711b0f9ac183b2507a58cf644081aafefdc025ce585f6a8dad427ca3baf55a19 django==2.2.17; python_version >= "3.7" and python_full_version < "4.0.0" and python_version < "4.0" \ --hash=sha256:558cb27930defd9a6042133258caf797b2d1dee233959f537e3dc475cb49bd7c \ --hash=sha256:cf5370a4d7765a9dd6d42a7b96b53c74f9446cd38209211304b210fe0404b861 diff --git a/conf/settings.py b/conf/settings.py index 5127053..e6090ca 100644 --- a/conf/settings.py +++ b/conf/settings.py @@ -11,9 +11,11 @@ from pathlib import Path as __Path +from django_ynh.secret_key import get_or_create_secret as __get_or_create_secret from inventory_project.settings.base import * # noqa -DEBUG = False + +DEBUG = False # Don't turn DEBUG on in production! # ----------------------------------------------------------------------------- @@ -31,40 +33,41 @@ PATH_URL = PATH_URL.strip('/') # ----------------------------------------------------------------------------- -ROOT_URLCONF = 'ynh_urls' # /opt/yunohost/pyinventory/ynh_urls.py +ROOT_URLCONF = 'urls' # /opt/yunohost/pyinventory/ynh_urls.py -# ----------------------------------------------------------------------------- +# Function that will be called to finalize a user profile: +YNH_SETUP_USER = 'setup_user.setup_project_user' + +SECRET_KEY = __get_or_create_secret(FINAL_HOME_PATH / 'secret.txt') # /opt/yunohost/$app/secret.txt + +INSTALLED_APPS.append('django_ynh') + +MIDDLEWARE.insert( + MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, + # login a user via HTTP_REMOTE_USER header from SSOwat: + 'django_ynh.sso_auth.auth_middleware.SSOwatRemoteUserMiddleware', +) # Keep ModelBackend around for per-user permissions and superuser AUTHENTICATION_BACKENDS = ( 'axes.backends.AxesBackend', # AxesBackend should be the first backend! - + # # Authenticate via SSO and nginx 'HTTP_REMOTE_USER' header: - 'ynh_authenticate.RemoteUserBackend', - + 'django_ynh.sso_auth.auth_backend.SSOwatUserBackend', + # # Fallback to normal Django model backend: 'django.contrib.auth.backends.ModelBackend', ) + LOGIN_REDIRECT_URL = None LOGIN_URL = '/yunohost/sso/' LOGOUT_REDIRECT_URL = '/yunohost/sso/' # /yunohost/sso/?action=logout -# ----------------------------------------------------------------------------- -# https://docs.djangoproject.com/en/2.2/howto/auth-remote-user/ -# Add RemoteUserMiddleware after AuthenticationMiddleware - -MIDDLEWARE.insert( - MIDDLEWARE.index('django.contrib.auth.middleware.AuthenticationMiddleware') + 1, - 'ynh_authenticate.RemoteUserMiddleware', -) - # ----------------------------------------------------------------------------- -ADMINS = ( - ('__ADMIN__', '__ADMINMAIL__'), -) +ADMINS = (('__ADMIN__', '__ADMINMAIL__'),) MANAGERS = ADMINS @@ -170,6 +173,7 @@ LOGGING = { 'django': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, 'axes': {'handlers': ['log_file', 'mail_admins'], 'level': 'WARNING', 'propagate': False}, 'django_tools': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, + 'django_ynh': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, 'inventory': {'handlers': ['log_file', 'mail_admins'], 'level': 'INFO', 'propagate': False}, }, } diff --git a/conf/setup_user.py b/conf/setup_user.py new file mode 100644 index 0000000..5d820de --- /dev/null +++ b/conf/setup_user.py @@ -0,0 +1,8 @@ +def setup_project_user(user): + """ + All users used the Django admin, so we need to set the "staff" user flag. + Called from django_ynh.sso_auth + """ + user.is_staff = True + user.save() + return user diff --git a/conf/urls.py b/conf/urls.py index bd1d685..c012497 100644 --- a/conf/urls.py +++ b/conf/urls.py @@ -23,9 +23,7 @@ if settings.PATH_URL: urlpatterns = [ # path(f'{settings.PATH_URL}/debug/', debug_view), path(f'{settings.PATH_URL}/', admin.site.urls), - path(f'{settings.PATH_URL}/ckeditor/', include('ckeditor_uploader.urls')), - # MEDIA_URL contains the "PATH_URL" already: path(settings.MEDIA_URL.lstrip('/'), include('django_tools.serve_media_app.urls')), ] diff --git a/conf/wsgi.py b/conf/wsgi.py index 6158012..018a0cc 100644 --- a/conf/wsgi.py +++ b/conf/wsgi.py @@ -2,7 +2,11 @@ WSGI config """ import os + + os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' -from django.core.wsgi import get_wsgi_application +from django.core.wsgi import get_wsgi_application # noqa + + application = get_wsgi_application() diff --git a/conf/ynh_authenticate.py b/conf/ynh_authenticate.py deleted file mode 100644 index 1f3d03c..0000000 --- a/conf/ynh_authenticate.py +++ /dev/null @@ -1,185 +0,0 @@ -""" - * remote user authentication backend - * remote user middleware - - Note: SSOwat/nginx add authentication headers: - - 'HTTP_AUTHORIZATION': 'Basic XXXXXXXXXXXXXXXX=' - 'HTTP_AUTH_USER': 'username' - 'HTTP_REMOTE_USER': 'username' - - Basic auth contains "{username}:{plaintext-password}" - - and we get SSOwat cookies like: - - 'HTTP_COOKIE': 'SSOwAuthUser=username; ' - 'SSOwAuthHash=593876aa66...99e69f88af1e; ' - 'SSOwAuthExpire=1609227697.998; ' - - * Login a user via HTTP_REMOTE_USER header, but check also username in: - * SSOwAuthUser - * HTTP_AUTH_USER - * HTTP_AUTHORIZATION (Basic auth) - * Create new users - * Update Email, First / Last name for existing users -""" -import base64 -import logging - -from axes.exceptions import AxesBackendPermissionDenied -from django.contrib.auth.backends import RemoteUserBackend as OriginRemoteUserBackend -from django.contrib.auth.middleware import RemoteUserMiddleware as OriginRemoteUserMiddleware -from django.core.exceptions import ValidationError -from inventory.permissions import get_or_create_normal_user_group - -logger = logging.getLogger(__name__) - - -def update_user_profile(request): - """ - Update existing user information: - * Email - * First / Last name - """ - user = request.user - assert user.is_authenticated - - update_fields = [] - - if not user.password: - # Empty password is not valid, so we can't save the model, because of full_clean() call - logger.info('Set unusable password for user: %s', user) - user.set_unusable_password() - update_fields.append('password') - - email = request.META.get('HTTP_EMAIL') - if email and user.email != email: - logger.info('Update email: %r -> %r', user.email, email) - user.email = email - update_fields.append('email') - - raw_username = request.META.get('HTTP_NAME') - if raw_username: - if ' ' in raw_username: - first_name, last_name = raw_username.split(' ', 1) - else: - first_name = '' - last_name = raw_username - - if user.first_name != first_name: - logger.info('Update first name: %r -> %r', user.first_name, first_name) - user.first_name = first_name - update_fields.append('first_name') - - if user.last_name != last_name: - logger.info('Update last name: %r -> %r', user.last_name, last_name) - user.last_name = last_name - update_fields.append('last_name') - - if update_fields: - try: - user.full_clean() - except ValidationError: - logger.exception('Can not update user: %s', user) - else: - user.save(update_fields=update_fields) - - -class RemoteUserMiddleware(OriginRemoteUserMiddleware): - """ - Middleware to login a user HTTP_REMOTE_USER header. - Use Django Axes if something is wrong. - Update exising user informations. - """ - header = 'HTTP_REMOTE_USER' - force_logout_if_no_header = True - - def process_request(self, request): - # Keep the information if the user is already logged in - was_authenticated = request.user.is_authenticated - - super().process_request(request) # login remote user - - if not request.user.is_authenticated: - # Not logged in -> nothing to verify here - return - - # Check SSOwat cookie informations: - try: - username = request.COOKIES['SSOwAuthUser'] - except KeyError: - logger.error('SSOwAuthUser cookie missing!') - - # emits a signal indicating user login failed, which is processed by - # axes.signals.log_user_login_failed which logs and flags the failed request. - raise AxesBackendPermissionDenied('Cookie missing') - - logger.info('SSOwat username from cookies: %r', username) - if username != request.user.username: - raise AxesBackendPermissionDenied('Wrong username') - - # Compare with HTTP_AUTH_USER - try: - username = request.META['HTTP_AUTH_USER'] - except KeyError: - logger.error('HTTP_AUTH_USER missing!') - raise AxesBackendPermissionDenied('No HTTP_AUTH_USER') - - if username != request.user.username: - raise AxesBackendPermissionDenied('Wrong HTTP_AUTH_USER username') - - # Also check 'HTTP_AUTHORIZATION', but only the username ;) - try: - auth = request.META['HTTP_AUTHORIZATION'] - except KeyError: - logger.error('HTTP_AUTHORIZATION missing!') - raise AxesBackendPermissionDenied('No HTTP_AUTHORIZATION') - - scheme, creds = auth.split(' ', 1) - if scheme.lower() != 'basic': - logger.error('HTTP_AUTHORIZATION with %r not supported', scheme) - raise AxesBackendPermissionDenied('HTTP_AUTHORIZATION scheme not supported') - - creds = str(base64.b64decode(creds), encoding='utf-8') - username = creds.split(':', 1)[0] - if username != request.user.username: - raise AxesBackendPermissionDenied('Wrong HTTP_AUTHORIZATION username') - - if not was_authenticated: - # First request, after login -> update user informations - logger.info('Remote used was logged in') - update_user_profile(request) - - -class RemoteUserBackend(OriginRemoteUserBackend): - """ - Authentication backend via SSO/nginx header - """ - create_unknown_user = True - - def authenticate(self, request, remote_user): - logger.info('Remote user authenticate: %r', remote_user) - return super().authenticate(request, remote_user) - - def configure_user(self, request, user): - """ - Configure a user after creation and return the updated user. - Setup a normal, non-superuser - """ - logger.warning('Configure user %s', user) - - user.set_unusable_password() # Always login via SSO - user.is_staff = True - user.is_superuser = False - user.save() - - pyinventory_user_group = get_or_create_normal_user_group()[0] - user.groups.set([pyinventory_user_group]) - - update_user_profile(request) - - return user - - def user_can_authenticate(self, user): - logger.warning('Remote user login: %s', user) - return True diff --git a/local_test.py b/local_test.py old mode 100755 new mode 100644 index 3430c3f..305294d --- a/local_test.py +++ b/local_test.py @@ -1,154 +1,29 @@ -#!/usr/bin/env python3 - """ - Start PyInventory in YunoHost setup locally. - Note: - You can only run this script, if you are in a activated PyInventory venv! + Build a "local_test" YunoHost installation and start the Django dev. server against it. + + Run via: + make local-test + see README for details ;) """ - -import os -import shlex -import subprocess -import sys from pathlib import Path -os.environ['DJANGO_SETTINGS_MODULE'] = 'ynh_pyinventory_settings' try: - import inventory_project # noqa + from django_ynh.local_test import create_local_test except ImportError as err: - raise ImportError( - 'Couldn\'t import PyInventory. Did you ' - 'forget to activate a virtual environment?' - ) from err + raise ImportError('Did you forget to activate a virtual environment?') from err - -BASE_PATH = Path(__file__).parent.absolute() -TEST_PATH = BASE_PATH / 'local_test' -CONF_PATH = BASE_PATH / 'conf' - -FINAL_HOME_PATH = TEST_PATH / 'opt_yunohost' -FINAL_WWW_PATH = TEST_PATH / 'var_www' -LOG_FILE = TEST_PATH / 'var_log_pyinventory.log' - -MANAGE_PY_FILE = CONF_PATH / 'manage.py' -CREATE_SUPERUSER_FILE = CONF_PATH / 'create_superuser.py' -SETTINGS_FILE = CONF_PATH / 'ynh_pyinventory_settings.py' -URLS_FILE = CONF_PATH / 'ynh_urls.py' - -REPLACES = { - '__FINAL_HOME_PATH__': str(FINAL_HOME_PATH), - '__FINAL_WWW_PATH__': str(FINAL_WWW_PATH), - '__LOG_FILE__': str(TEST_PATH / 'var_log_pyinventory.log'), - - '__PATH_URL__': 'app_path', - '__DOMAIN__': '127.0.0.1', - - 'django.db.backends.postgresql': 'django.db.backends.sqlite3', - "'NAME': '__APP__',": f"'NAME': '{TEST_PATH / 'test_db.sqlite'}',", - - 'django_redis.cache.RedisCache': 'django.core.cache.backends.dummy.DummyCache', - - 'DEBUG = False': 'DEBUG = True', - - # Just use the default logging setup from PyInventory project: - 'LOGGING = {': 'HACKED_DEACTIVATED_LOGGING = {', -} - - -def verbose_check_call(command, verbose=True, **kwargs): - """ 'verbose' version of subprocess.check_call() """ - if verbose: - print('_' * 100) - msg = f'Call: {command!r}' - verbose_kwargs = ', '.join(f'{k}={v!r}' for k, v in sorted(kwargs.items())) - if verbose_kwargs: - msg += f' (kwargs: {verbose_kwargs})' - print(f'{msg}\n', flush=True) - - env = os.environ.copy() - env['PYTHONUNBUFFERED'] = '1' - - popenargs = shlex.split(command) - subprocess.check_call( - popenargs, - universal_newlines=True, - env=env, - **kwargs - ) - - -def call_manage_py(args): - verbose_check_call( - command=f'{sys.executable} manage.py {args}', - cwd=FINAL_HOME_PATH, - ) - - -def copy_patch(src_file, replaces=None): - dst_file = FINAL_HOME_PATH / src_file.name - print(f'{src_file.relative_to(BASE_PATH)} -> {dst_file.relative_to(BASE_PATH)}') - - with src_file.open('r') as f: - content = f.read() - - if replaces: - for old, new in replaces.items(): - content = content.replace(old, new) - - with dst_file.open('w') as f: - f.write(content) +BASE_PATH = Path(__file__).parent def main(): - print('-' * 100) - - assert BASE_PATH.is_dir() - assert CONF_PATH.is_dir() - assert SETTINGS_FILE.is_file() - assert URLS_FILE.is_file() - - for p in (TEST_PATH, FINAL_HOME_PATH, FINAL_WWW_PATH): - if p.is_dir(): - print(f'Already exists: "{p.relative_to(BASE_PATH)}", ok.') - else: - print(f'Create: "{p.relative_to(BASE_PATH)}"') - p.mkdir(parents=True, exist_ok=True) - - LOG_FILE.touch(exist_ok=True) - - # conf/manage.py -> local_test/manage.py - copy_patch(src_file=MANAGE_PY_FILE) - - # conf/create_superuser.py -> local_test/opt_yunohost/create_superuser.py - copy_patch(src_file=CREATE_SUPERUSER_FILE) - - # conf/ynh_pyinventory_settings.py -> local_test/ynh_pyinventory_settings.py - copy_patch(src_file=SETTINGS_FILE, replaces=REPLACES) - - # conf/ynh_urls.py -> local_test/ynh_urls.py - copy_patch(src_file=URLS_FILE, replaces=REPLACES) - - with Path(FINAL_HOME_PATH / 'local_settings.py').open('w') as f: - f.write('# Only for local test run\n') - f.write('SERVE_FILES=True # used in src/inventory_project/urls.py\n') - - # call "local_test/manage.py" via subprocess: - call_manage_py('check --deploy') - call_manage_py('migrate --no-input') - call_manage_py('collectstatic --no-input') - - verbose_check_call( - command=f'{sys.executable} create_superuser.py --username="test" --password="test"', - cwd=FINAL_HOME_PATH, + create_local_test( + django_settings_path=BASE_PATH / 'conf' / 'settings.py', + destination=BASE_PATH / 'local_test', + runserver=True, ) - try: - call_manage_py('runserver --nostatic') - except KeyboardInterrupt: - print('\nBye ;)') - if __name__ == '__main__': main() diff --git a/manifest.json b/manifest.json index 167d761..de98baa 100644 --- a/manifest.json +++ b/manifest.json @@ -5,7 +5,7 @@ "description": { "en": "Web based management to catalog things including state and location etc." }, - "version": "0.8.2~ynh4", + "version": "0.8.2~ynh5", "url": "https://github.com/jedie/PyInventory", "license": "GPL-3.0", "maintainer": { diff --git a/pyproject.toml b/pyproject.toml index 103fa6f..0fadb29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyinventory_ynh" -version = "0.8.2~ynh4" +version = "0.8.2~ynh5" description = "Test pyinventory_ynh via local_test.py" authors = ["JensDiemer "] license = "GPL" @@ -11,7 +11,72 @@ pyinventory = "*" django_ynh = "*" [tool.poetry.dev-dependencies] +bx_py_utils = "*" +isort = "*" +flake8 = "*" +flynt = "*" +black = "*" +pyupgrade = "*" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + + +[tool.isort] +# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format +atomic=true +line_length=120 +case_sensitive=false +skip_glob=["*/htmlcov/*","*/migrations/*"] +multi_line_output=3 +include_trailing_comma=true +known_first_party=["inventory"] +no_lines_before="LOCALFOLDER" +default_section="THIRDPARTY" +sections=["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"] +lines_after_imports=2 + + +[tool.pytest.ini_options] +# https://docs.pytest.org/en/latest/customize.html#pyproject-toml +minversion = "6.0" +norecursedirs = ".* .git __pycache__ conf coverage* dist htmlcov" +# sometimes helpfull "addopts" arguments: +# -vv +# --verbose +# --capture=no +# --trace-config +# --full-trace +# -p no:warnings +addopts = """ + --import-mode=importlib + --reuse-db + --nomigrations + --cov=. + --cov-report term-missing + --cov-report html + --cov-report xml + --no-cov-on-fail + --showlocals + --doctest-modules + --failed-first + --last-failed-no-failures all + --new-first +""" + + +[tool.tox] +# https://tox.readthedocs.io/en/latest/example/basic.html#pyproject-toml-tox-legacy-ini +legacy_tox_ini = """ +[tox] +isolated_build = True +envlist = py39,py38,py37 +skip_missing_interpreters = True + +[testenv] +passenv = * +whitelist_externals = make +commands = + make pytest +""" diff --git a/scripts/_common.sh b/scripts/_common.sh index 826fe53..dc3de28 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -27,9 +27,6 @@ log_file="${log_path}/pyinventory.log" # dependencies used by the app pkg_dependencies="build-essential python3-dev python3-pip python3-venv git postgresql postgresql-contrib" -# PyInventory's version for PIP and settings file -pyinventory_version="0.8.2" - #================================================= # Redis HELPERS #================================================= diff --git a/scripts/change_url b/scripts/change_url index 4fbcb36..2b32057 100644 --- a/scripts/change_url +++ b/scripts/change_url @@ -116,10 +116,10 @@ fi ynh_script_progression --message="Modify PyInventory's config file..." # save old settings file -settings="$final_path/ynh_pyinventory_settings.py" +settings="$final_path/settings.py" ynh_backup_if_checksum_is_different --file="$settings" -cp "../conf/ynh_pyinventory_settings.py" "$settings" +cp "../conf/settings.py" "$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" diff --git a/scripts/install b/scripts/install index 4aece0f..c309ec8 100755 --- a/scripts/install +++ b/scripts/install @@ -101,7 +101,7 @@ ynh_system_user_create --username="$app" --home_dir="$final_path" --use_shell #================================================= # PIP INSTALLATION #================================================= -ynh_script_progression --message="Install PyInventory using PIP..." --weight=80 +ynh_script_progression --message="Install project via pip..." --weight=80 python3 -m venv "${final_path}/venv" cp ../conf/requirements.txt "$final_path/requirements.txt" @@ -119,10 +119,7 @@ chown -R "$app" "$final_path" #================================================= # copy config files # ================================================ -ynh_script_progression --message="Create pyinventory configuration file..." - -cp ../conf/create_superuser.py "$final_path/create_superuser.py" -chmod +x "$final_path/create_superuser.py" +ynh_script_progression --message="Create project configuration files..." gunicorn_conf="$final_path/gunicorn.conf.py" cp "../conf/gunicorn.conf.py" "$gunicorn_conf" @@ -134,10 +131,8 @@ ynh_store_file_checksum --file="$gunicorn_conf" cp ../conf/manage.py "$final_path/manage.py" chmod +x "$final_path/manage.py" -cp ../conf/wsgi.py "$final_path/wsgi.py" - -settings="$final_path/ynh_pyinventory_settings.py" -cp "../conf/ynh_pyinventory_settings.py" "$settings" +settings="$final_path/settings.py" +cp "../conf/settings.py" "$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" @@ -156,10 +151,11 @@ ynh_store_file_checksum --file="$settings" ynh_app_setting_set --app="$app" --key=redis_db --value="$redis_db" -touch "$final_path/local_settings.py" +cp ../conf/setup_user.py "$final_path/setup_user.py" +cp ../conf/urls.py "$final_path/urls.py" +cp ../conf/wsgi.py "$final_path/wsgi.py" -cp "../conf/ynh_authenticate.py" "$final_path/ynh_authenticate.py" -cp "../conf/ynh_urls.py" "$final_path/ynh_urls.py" +touch "$final_path/local_settings.py" #================================================= # MIGRATE / COLLECTSTATIC / CREATEADMIN @@ -177,7 +173,9 @@ ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight ./manage.py migrate --no-input ./manage.py collectstatic --no-input - ./create_superuser.py --username="$admin" --email="$admin_mail" --password="pyinventory" + + # Create/update Django superuser (set unusable password, because auth done via SSOwat): + ./manage.py create_superuser --username="$admin" --email="$admin_mail" # Check the configuration # This may fail in some cases with errors, etc., but the app works and the user can fix issues later. diff --git a/scripts/upgrade b/scripts/upgrade index 4295ece..e45aba9 100755 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -83,10 +83,9 @@ ynh_script_progression --message="Configuring a systemd service..." ynh_add_systemd_config --service="$app" --template="pyinventory.service" #================================================= -# UPGRADE PYINVENTORY +# UPGRADE VENV #================================================= - -ynh_script_progression --message="Install pyinventory using PIP..." --weight=15 +ynh_script_progression --message="Upgrade project via pip..." --weight=80 python3 -m venv "${final_path}/venv" cp ../conf/requirements.txt "$final_path/requirements.txt" @@ -94,21 +93,17 @@ chown -R "$app" "$final_path" #run source in a 'sub shell' ( - set +o nounset - source "${final_path}/venv/bin/activate" - set -o nounset - ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip - ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt" + set +o nounset + source "${final_path}/venv/bin/activate" + set -o nounset + ynh_exec_as $app $final_path/venv/bin/pip install --upgrade pip + ynh_exec_as $app $final_path/venv/bin/pip install -r "$final_path/requirements.txt" ) #================================================= # copy config files # ================================================ -ynh_script_progression --message="Create pyinventory configuration file..." - -ynh_backup_if_checksum_is_different --file="$final_path/create_superuser.py" -cp ../conf/create_superuser.py "$final_path/create_superuser.py" -chmod +x "$final_path/create_superuser.py" +ynh_script_progression --message="Create project configuration files..." gunicorn_conf="$final_path/gunicorn.conf.py" ynh_backup_if_checksum_is_different --file="$gunicorn_conf" @@ -122,14 +117,11 @@ ynh_backup_if_checksum_is_different --file="$final_path/manage.py" cp ../conf/manage.py "$final_path/manage.py" chmod +x "$final_path/manage.py" -ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py" -cp ../conf/wsgi.py "$final_path/wsgi.py" - # save old settings file -settings="$final_path/ynh_pyinventory_settings.py" +settings="$final_path/settings.py" ynh_backup_if_checksum_is_different --file="$settings" -cp "../conf/ynh_pyinventory_settings.py" "$settings" +cp "../conf/settings.py" "$settings" ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$settings" ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$settings" @@ -146,10 +138,16 @@ ynh_replace_string --match_string="__PATH_URL__" --replace_string="$path_url" -- # Recalculate and store the config file checksum into the app settings ynh_store_file_checksum --file="$settings" -touch "$final_path/local_settings.py" +ynh_backup_if_checksum_is_different --file="$final_path/setup_user.py" +cp ../conf/setup_user.py "$final_path/setup_user.py" -cp "../conf/ynh_authenticate.py" "$final_path/ynh_authenticate.py" -cp "../conf/ynh_urls.py" "$final_path/ynh_urls.py" +ynh_backup_if_checksum_is_different --file="$final_path/urls.py" +cp ../conf/urls.py "$final_path/urls.py" + +ynh_backup_if_checksum_is_different --file="$final_path/wsgi.py" +cp ../conf/wsgi.py "$final_path/wsgi.py" + +touch "$final_path/local_settings.py" #================================================= # MIGRATE PYINVENTORY @@ -167,7 +165,9 @@ ynh_script_progression --message="migrate/collectstatic/createadmin..." --weight ./manage.py migrate --no-input ./manage.py collectstatic --no-input - ./create_superuser.py --username="$admin" --email="$admin_mail" --password="pyinventory" + + # Create/update Django superuser (set unusable password, because auth done via SSOwat): + ./manage.py create_superuser --username="$admin" --email="$admin_mail" # Check the configuration # This may fail in some cases with errors, etc., but the app works and the user can fix issues later.