c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c2cgeoportal_geoportal/__init__.py +960 -0
- c2cgeoportal_geoportal/lib/__init__.py +256 -0
- c2cgeoportal_geoportal/lib/authentication.py +250 -0
- c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
- c2cgeoportal_geoportal/lib/cacheversion.py +75 -0
- c2cgeoportal_geoportal/lib/caching.py +176 -0
- c2cgeoportal_geoportal/lib/check_collector.py +80 -0
- c2cgeoportal_geoportal/lib/checker.py +295 -0
- c2cgeoportal_geoportal/lib/common_headers.py +170 -0
- c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
- c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
- c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
- c2cgeoportal_geoportal/lib/functionality.py +166 -0
- c2cgeoportal_geoportal/lib/headers.py +62 -0
- c2cgeoportal_geoportal/lib/i18n.py +38 -0
- c2cgeoportal_geoportal/lib/layers.py +132 -0
- c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
- c2cgeoportal_geoportal/lib/loader.py +57 -0
- c2cgeoportal_geoportal/lib/metrics.py +117 -0
- c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
- c2cgeoportal_geoportal/lib/oidc.py +304 -0
- c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
- c2cgeoportal_geoportal/lib/xsd.py +166 -0
- c2cgeoportal_geoportal/py.typed +0 -0
- c2cgeoportal_geoportal/resources.py +49 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
- c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +511 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +59 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_left.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +43 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
- c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +67 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +295 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +922 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
- c2cgeoportal_geoportal/scripts/__init__.py +64 -0
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
- c2cgeoportal_geoportal/scripts/create_demo_theme.py +80 -0
- c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
- c2cgeoportal_geoportal/scripts/pcreate.py +314 -0
- c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
- c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
- c2cgeoportal_geoportal/templates/login.html +90 -0
- c2cgeoportal_geoportal/templates/notlogin.html +62 -0
- c2cgeoportal_geoportal/templates/testi18n.html +12 -0
- c2cgeoportal_geoportal/views/__init__.py +59 -0
- c2cgeoportal_geoportal/views/dev.py +57 -0
- c2cgeoportal_geoportal/views/dynamic.py +208 -0
- c2cgeoportal_geoportal/views/entry.py +174 -0
- c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
- c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
- c2cgeoportal_geoportal/views/i18n.py +129 -0
- c2cgeoportal_geoportal/views/layers.py +713 -0
- c2cgeoportal_geoportal/views/login.py +679 -0
- c2cgeoportal_geoportal/views/mapserverproxy.py +191 -0
- c2cgeoportal_geoportal/views/memory.py +90 -0
- c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
- c2cgeoportal_geoportal/views/pdfreport.py +245 -0
- c2cgeoportal_geoportal/views/printproxy.py +143 -0
- c2cgeoportal_geoportal/views/profile.py +127 -0
- c2cgeoportal_geoportal/views/proxy.py +259 -0
- c2cgeoportal_geoportal/views/raster.py +193 -0
- c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
- c2cgeoportal_geoportal/views/shortener.py +152 -0
- c2cgeoportal_geoportal/views/theme.py +1322 -0
- c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
- c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc2.dist-info}/METADATA +21 -24
- c2cgeoportal_geoportal-2.9rc2.dist-info/RECORD +192 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc2.dist-info}/WHEEL +1 -1
- c2cgeoportal_geoportal-2.9rc2.dist-info/entry_points.txt +28 -0
- c2cgeoportal_geoportal-2.9rc2.dist-info/top_level.txt +2 -0
- tests/__init__.py +100 -0
- tests/test_cachebuster.py +71 -0
- tests/test_caching.py +275 -0
- tests/test_checker.py +85 -0
- tests/test_decimaljson.py +47 -0
- tests/test_headerstween.py +64 -0
- tests/test_i18n.py +31 -0
- tests/test_init.py +193 -0
- tests/test_locale_negociator.py +69 -0
- tests/test_mapserverproxy_route_predicate.py +64 -0
- tests/test_raster.py +267 -0
- tests/test_wmstparsing.py +238 -0
- tests/xmlstr.py +103 -0
- c2cgeoportal_geoportal-2.3.5.80.dist-info/DESCRIPTION.rst +0 -8
- c2cgeoportal_geoportal-2.3.5.80.dist-info/RECORD +0 -7
- c2cgeoportal_geoportal-2.3.5.80.dist-info/entry_points.txt +0 -22
- c2cgeoportal_geoportal-2.3.5.80.dist-info/metadata.json +0 -1
- c2cgeoportal_geoportal-2.3.5.80.dist-info/top_level.txt +0 -1
@@ -0,0 +1,879 @@
|
|
1
|
+
# Copyright (c) 2014-2024, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
|
29
|
+
import argparse
|
30
|
+
import atexit
|
31
|
+
import filecmp
|
32
|
+
import os
|
33
|
+
import re
|
34
|
+
import shutil
|
35
|
+
import subprocess
|
36
|
+
import sys
|
37
|
+
from argparse import ArgumentParser, Namespace
|
38
|
+
from collections.abc import Callable
|
39
|
+
from json.decoder import JSONDecodeError
|
40
|
+
from subprocess import call, check_call, check_output
|
41
|
+
from typing import Any, Union, cast
|
42
|
+
|
43
|
+
import pkg_resources
|
44
|
+
import requests
|
45
|
+
import yaml
|
46
|
+
|
47
|
+
from c2cgeoportal_geoportal.lib.bashcolor import Color, colorize
|
48
|
+
|
49
|
+
REQUIRED_TEMPLATE_KEYS = ["package", "srid", "extent"]
|
50
|
+
TEMPLATE_EXAMPLE = {"package": "${package}", "srid": "${srid}", "extent": "489246, 78873, 837119, 296543"}
|
51
|
+
DIFF_NOTICE = (
|
52
|
+
"You should apply the changes shown in the diff file on `CONST_create_template/<file>` "
|
53
|
+
"on your project's `<file>`.\n"
|
54
|
+
"Some advice to be more efficient: if the changes on a file concern a file that you never customize, "
|
55
|
+
"you can simply copy the new file from `CONST_create_template` "
|
56
|
+
"(`cp CONST_create_template/<file> <file>`)."
|
57
|
+
"You can furthermore add this file to the `unmanaged_files` section of the `project.yaml` file, "
|
58
|
+
"to avoid its contents appearing in the diff file for the next upgrade."
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
def fix_style() -> None:
|
63
|
+
"""Fix the style of all the project files using isort, Black and Prettier."""
|
64
|
+
|
65
|
+
file_to_clean = []
|
66
|
+
for filename, content in (
|
67
|
+
(".prettierignore", "*.min.js\n"),
|
68
|
+
("pyproject.toml", "[tool.black]\nline-length = 110\ntarget-version = ['py39']\n"),
|
69
|
+
(".prettierrc.yaml", "bracketSpacing: false\nquoteProps: preserve\n"),
|
70
|
+
(
|
71
|
+
".editorconfig",
|
72
|
+
"""root = true
|
73
|
+
[*]
|
74
|
+
max_line_length = 110
|
75
|
+
""",
|
76
|
+
),
|
77
|
+
):
|
78
|
+
if not os.path.exists(filename):
|
79
|
+
file_to_clean.append(filename)
|
80
|
+
if os.path.exists(os.path.join("CONST_create_template", filename)):
|
81
|
+
shutil.copyfile(os.path.join("CONST_create_template", filename), filename)
|
82
|
+
else:
|
83
|
+
with open(filename, "w", encoding="utf8") as file_:
|
84
|
+
file_.write(content)
|
85
|
+
|
86
|
+
if os.path.exists("ci/config.yaml"):
|
87
|
+
os.rename("ci/config.yaml", "ci/config.yaml_")
|
88
|
+
if os.path.exists(".pre-commit-config.yaml"):
|
89
|
+
print("Run pre-commit to fix the style.")
|
90
|
+
sys.stdout.flush()
|
91
|
+
subprocess.run(["pre-commit", "run", "--all-files"]) # pylint: disable=subprocess-run-check
|
92
|
+
else:
|
93
|
+
print("Run c2cciutils-checks to fix the style.")
|
94
|
+
sys.stdout.flush()
|
95
|
+
subprocess.run( # pylint: disable=subprocess-run-check
|
96
|
+
["c2cciutils-checks", "--fix", "--check=isort"]
|
97
|
+
)
|
98
|
+
subprocess.run( # pylint: disable=subprocess-run-check
|
99
|
+
["c2cciutils-checks", "--fix", "--check=black"]
|
100
|
+
)
|
101
|
+
subprocess.run( # pylint: disable=subprocess-run-check
|
102
|
+
["c2cciutils-checks", "--fix", "--check=prettier"]
|
103
|
+
)
|
104
|
+
if os.path.exists("ci/config.yaml_"):
|
105
|
+
os.rename("ci/config.yaml_", "ci/config.yaml")
|
106
|
+
|
107
|
+
for filename in file_to_clean:
|
108
|
+
os.remove(filename)
|
109
|
+
|
110
|
+
|
111
|
+
def main() -> None:
|
112
|
+
"""Tool used to do the application upgrade."""
|
113
|
+
parser = _fill_arguments()
|
114
|
+
options = parser.parse_args()
|
115
|
+
|
116
|
+
c2cupgradetool = C2cUpgradeTool(options)
|
117
|
+
c2cupgradetool.upgrade()
|
118
|
+
|
119
|
+
|
120
|
+
def _fill_arguments() -> ArgumentParser:
|
121
|
+
parser = ArgumentParser()
|
122
|
+
parser.add_argument(
|
123
|
+
"--git-remote", metavar="GITREMOTE", help="Specify the remote branch", default="origin"
|
124
|
+
)
|
125
|
+
parser.add_argument("--step", type=int, help=argparse.SUPPRESS, default=0)
|
126
|
+
|
127
|
+
return parser
|
128
|
+
|
129
|
+
|
130
|
+
class InteruptedException(Exception):
|
131
|
+
"""The interrupted exception."""
|
132
|
+
|
133
|
+
|
134
|
+
_CURRENT_STEP_NUMBER = 0
|
135
|
+
|
136
|
+
|
137
|
+
class Step:
|
138
|
+
"""Decorator used for en upgrade step."""
|
139
|
+
|
140
|
+
def __init__(self, step_number: int, file_marker: bool = True):
|
141
|
+
global _CURRENT_STEP_NUMBER # pylint: disable=global-statement
|
142
|
+
_CURRENT_STEP_NUMBER = step_number
|
143
|
+
self.step_number = step_number
|
144
|
+
self.file_marker = file_marker
|
145
|
+
|
146
|
+
def __call__(
|
147
|
+
self, current_step: Callable[["C2cUpgradeTool", int], None]
|
148
|
+
) -> Callable[["C2cUpgradeTool"], None]:
|
149
|
+
def decorate(c2cupgradetool: "C2cUpgradeTool") -> None:
|
150
|
+
try:
|
151
|
+
if os.path.isfile(f".UPGRADE{self.step_number - 1}"):
|
152
|
+
os.unlink(f".UPGRADE{self.step_number - 1}")
|
153
|
+
if self.file_marker:
|
154
|
+
with open(f".UPGRADE{self.step_number}", "w", encoding="utf8"):
|
155
|
+
pass
|
156
|
+
print(f"Start step {self.step_number}.")
|
157
|
+
sys.stdout.flush()
|
158
|
+
current_step(c2cupgradetool, self.step_number)
|
159
|
+
except subprocess.CalledProcessError as exception:
|
160
|
+
command = " ".join([f"'{exception}'" for exception in exception.cmd])
|
161
|
+
c2cupgradetool.print_step(
|
162
|
+
self.step_number,
|
163
|
+
error=True,
|
164
|
+
message=f"The command `{command}` returns the error code {exception.returncode}.",
|
165
|
+
prompt="Fix the error and run the step again:",
|
166
|
+
)
|
167
|
+
sys.exit(1)
|
168
|
+
except InteruptedException as exception:
|
169
|
+
c2cupgradetool.print_step(
|
170
|
+
self.step_number,
|
171
|
+
error=True,
|
172
|
+
message=f"There was an error: {exception}.",
|
173
|
+
prompt="Fix the error and run the step again:",
|
174
|
+
)
|
175
|
+
sys.exit(1)
|
176
|
+
except Exception as exception:
|
177
|
+
catch_exception = exception
|
178
|
+
|
179
|
+
if self.step_number == _CURRENT_STEP_NUMBER:
|
180
|
+
|
181
|
+
def message() -> None:
|
182
|
+
c2cupgradetool.print_step(
|
183
|
+
self.step_number,
|
184
|
+
error=True,
|
185
|
+
message=f"The step had the error '{catch_exception}'.",
|
186
|
+
prompt="Fix the error and run the step again:",
|
187
|
+
)
|
188
|
+
|
189
|
+
atexit.register(message)
|
190
|
+
raise
|
191
|
+
|
192
|
+
return decorate
|
193
|
+
|
194
|
+
|
195
|
+
class C2cUpgradeTool:
|
196
|
+
"""The tool used to upgrade the application."""
|
197
|
+
|
198
|
+
color_bar = colorize("================================================================", Color.GREEN)
|
199
|
+
|
200
|
+
def __init__(self, options: Namespace):
|
201
|
+
self.options = options
|
202
|
+
self.project = self.get_project()
|
203
|
+
|
204
|
+
@staticmethod
|
205
|
+
def get_project() -> dict[str, Any]:
|
206
|
+
if not os.path.isfile("project.yaml"):
|
207
|
+
print(colorize("Unable to find the required 'project.yaml' file.", Color.RED))
|
208
|
+
sys.exit(1)
|
209
|
+
|
210
|
+
with open("project.yaml", encoding="utf8") as project_file:
|
211
|
+
return cast(dict[str, Any], yaml.safe_load(project_file))
|
212
|
+
|
213
|
+
@staticmethod
|
214
|
+
def get_upgrade(section: str) -> list[Any] | dict[str, Any]:
|
215
|
+
if not os.path.isfile(".upgrade.yaml"):
|
216
|
+
print(colorize("Unable to find the required '.upgrade.yaml' file.", Color.RED))
|
217
|
+
sys.exit(1)
|
218
|
+
|
219
|
+
with open(".upgrade.yaml", encoding="utf8") as project_file:
|
220
|
+
return cast(Union[list[Any], dict[str, Any]], yaml.safe_load(project_file)[section])
|
221
|
+
|
222
|
+
def print_step(
|
223
|
+
self,
|
224
|
+
step: int,
|
225
|
+
error: bool = False,
|
226
|
+
message: str | None = None,
|
227
|
+
prompt: str = "To continue, type:",
|
228
|
+
) -> None:
|
229
|
+
with open(".UPGRADE_INSTRUCTIONS", "w", encoding="utf8") as instructions:
|
230
|
+
print("")
|
231
|
+
print(self.color_bar)
|
232
|
+
if message is not None:
|
233
|
+
print(colorize(message, Color.RED if error else Color.YELLOW))
|
234
|
+
instructions.write(f"{message}\n")
|
235
|
+
if step >= 0:
|
236
|
+
print(colorize(prompt, Color.GREEN))
|
237
|
+
instructions.write(f"{prompt}\n")
|
238
|
+
cmd = ["./upgrade", os.environ["VERSION"]]
|
239
|
+
if step != 0:
|
240
|
+
cmd.append(f"{step}")
|
241
|
+
print(colorize(" ".join(cmd), Color.GREEN))
|
242
|
+
instructions.write(f"{' '.join(cmd)}\n")
|
243
|
+
|
244
|
+
def run_step(self, step: int) -> None:
|
245
|
+
getattr(self, f"step{step}")()
|
246
|
+
|
247
|
+
def test_checkers(self) -> tuple[bool, str | None]:
|
248
|
+
headers = " ".join(
|
249
|
+
[f"--header {i[0]}={i[1]}" for i in self.project.get("checker_headers", {}).items()]
|
250
|
+
)
|
251
|
+
run_curl = f"Run `curl --insecure {headers} '{self.project['checker_url']}'` for more information."
|
252
|
+
try:
|
253
|
+
requests.packages.urllib3.disable_warnings() # type: ignore
|
254
|
+
resp = requests.get(
|
255
|
+
self.project["checker_url"],
|
256
|
+
headers=self.project.get("checker_headers"),
|
257
|
+
verify=False, # nosec
|
258
|
+
timeout=120,
|
259
|
+
)
|
260
|
+
except requests.exceptions.ConnectionError as exception:
|
261
|
+
return False, "\n".join([f"Connection error: {exception}", run_curl])
|
262
|
+
except ConnectionRefusedError as exception:
|
263
|
+
return False, "\n".join([f"Connection refused: {exception}", run_curl])
|
264
|
+
if resp.status_code < 200 or resp.status_code >= 300:
|
265
|
+
print(colorize("=============", Color.RED))
|
266
|
+
print(colorize("Checker error", Color.RED))
|
267
|
+
try:
|
268
|
+
for name, value in resp.json()["failures"].items():
|
269
|
+
print(colorize(f"Test '{name}' failed with result:", Color.YELLOW))
|
270
|
+
del value["level"]
|
271
|
+
del value["timing"]
|
272
|
+
|
273
|
+
print(yaml.dump(value) if value != {} else "No result")
|
274
|
+
except JSONDecodeError:
|
275
|
+
print(
|
276
|
+
colorize(
|
277
|
+
f"Response is not a JSON '{resp.text}', {resp.reason} {resp.status_code}",
|
278
|
+
Color.RED,
|
279
|
+
)
|
280
|
+
)
|
281
|
+
|
282
|
+
return False, "\n".join(["Checker error:", run_curl])
|
283
|
+
|
284
|
+
return True, None
|
285
|
+
|
286
|
+
def upgrade(self) -> None:
|
287
|
+
self.run_step(self.options.step)
|
288
|
+
|
289
|
+
@Step(0, file_marker=False)
|
290
|
+
def step0(self, step: int) -> None:
|
291
|
+
project_template_keys = list(cast(dict[str, Any], self.project.get("template_vars")).keys())
|
292
|
+
messages = []
|
293
|
+
for required in REQUIRED_TEMPLATE_KEYS:
|
294
|
+
if required not in project_template_keys:
|
295
|
+
messages.append(
|
296
|
+
"The element '{required}' is missing in the `template_vars` of "
|
297
|
+
"the file 'project.yaml', you should have for example: {required}: {template}.".format(
|
298
|
+
required=required, template=TEMPLATE_EXAMPLE.get("required", "")
|
299
|
+
)
|
300
|
+
)
|
301
|
+
if self.project.get("managed_files") is None:
|
302
|
+
messages.append(
|
303
|
+
"The element `managed_files` is missing in the file 'project.yaml', "
|
304
|
+
"you must define this element with a list of regular expressions or with an empty array. "
|
305
|
+
"See upgrade documentation for more information."
|
306
|
+
)
|
307
|
+
if messages:
|
308
|
+
self.print_step(
|
309
|
+
step, error=True, message="\n".join(messages), prompt="Fix it and run again the upgrade:"
|
310
|
+
)
|
311
|
+
sys.exit(1)
|
312
|
+
|
313
|
+
if check_git_status_output() == "":
|
314
|
+
self.run_step(step + 1)
|
315
|
+
else:
|
316
|
+
check_call(["git", "status"])
|
317
|
+
self.print_step(
|
318
|
+
step + 1,
|
319
|
+
message="Here is the output of 'git status'. Please make sure to commit all your "
|
320
|
+
"changes before going further. All uncommitted changes will be lost.\n"
|
321
|
+
"Note that for debugging purpose it is possible to pass directly to step 2 "
|
322
|
+
f"e.-g.: ./upgrade --debug=../c2cgeoportal {os.environ['VERSION']} 2",
|
323
|
+
)
|
324
|
+
|
325
|
+
@Step(1)
|
326
|
+
def step1(self, step: int) -> None:
|
327
|
+
shutil.copyfile("project.yaml", "/tmp/project.yaml")
|
328
|
+
try:
|
329
|
+
check_call(["git", "reset", "--hard"])
|
330
|
+
check_call(["git", "clean", "--force", "-d"])
|
331
|
+
finally:
|
332
|
+
shutil.copyfile("/tmp/project.yaml", "project.yaml")
|
333
|
+
|
334
|
+
self.run_step(step + 1)
|
335
|
+
|
336
|
+
@Step(2)
|
337
|
+
def step2(self, step: int) -> None:
|
338
|
+
fix_style()
|
339
|
+
subprocess.run(["git", "add", "-A"]) # pylint: disable=subprocess-run-check
|
340
|
+
subprocess.run(["git", "commit", "--message=Run code style"]) # pylint: disable=subprocess-run-check
|
341
|
+
|
342
|
+
self.run_step(step + 1)
|
343
|
+
|
344
|
+
@Step(3)
|
345
|
+
def step3(self, step: int) -> None:
|
346
|
+
project_path = os.path.join("/tmp", self.project["project_folder"])
|
347
|
+
os.mkdir(project_path)
|
348
|
+
shutil.copyfile("/src/project.yaml", os.path.join(project_path, "project.yaml"))
|
349
|
+
check_call(
|
350
|
+
[
|
351
|
+
"pcreate",
|
352
|
+
"--overwrite",
|
353
|
+
"--scaffold=update",
|
354
|
+
project_path,
|
355
|
+
]
|
356
|
+
)
|
357
|
+
if self.get_project().get("advance", False):
|
358
|
+
check_call(
|
359
|
+
[
|
360
|
+
"pcreate",
|
361
|
+
"--overwrite",
|
362
|
+
"--scaffold=advance_update",
|
363
|
+
project_path,
|
364
|
+
]
|
365
|
+
)
|
366
|
+
|
367
|
+
shutil.copyfile(os.path.join(project_path, ".upgrade.yaml"), ".upgrade.yaml")
|
368
|
+
for upgrade_file in cast(list[dict[str, Any]], self.get_upgrade("upgrade_files")):
|
369
|
+
action = upgrade_file["action"]
|
370
|
+
if action == "remove":
|
371
|
+
self.files_to_remove(upgrade_file, prefix="CONST_create_template", force=True)
|
372
|
+
if action == "move":
|
373
|
+
self.files_to_move(upgrade_file, prefix="CONST_create_template", force=True)
|
374
|
+
|
375
|
+
shutil.rmtree(project_path)
|
376
|
+
os.remove(".upgrade.yaml")
|
377
|
+
|
378
|
+
check_call(["git", "add", "--all", "--force", "CONST_create_template/"])
|
379
|
+
call(["git", "commit", "--message=Perform the move into the CONST_create_template folder"])
|
380
|
+
|
381
|
+
self.run_step(step + 1)
|
382
|
+
|
383
|
+
@Step(4)
|
384
|
+
def step4(self, step: int) -> None:
|
385
|
+
if os.path.exists("CONST_create_template"):
|
386
|
+
check_call(["git", "rm", "-r", "--force", "CONST_create_template/"])
|
387
|
+
|
388
|
+
project_path = os.path.join("/tmp", self.project["project_folder"])
|
389
|
+
check_call(["ln", "-s", "/src", project_path])
|
390
|
+
check_call(
|
391
|
+
[
|
392
|
+
"pcreate",
|
393
|
+
"--overwrite",
|
394
|
+
"--scaffold=update",
|
395
|
+
project_path,
|
396
|
+
]
|
397
|
+
)
|
398
|
+
if self.get_project().get("advance", False):
|
399
|
+
check_call(
|
400
|
+
[
|
401
|
+
"pcreate",
|
402
|
+
"--overwrite",
|
403
|
+
"--scaffold=advance_update",
|
404
|
+
project_path,
|
405
|
+
]
|
406
|
+
)
|
407
|
+
os.remove(project_path)
|
408
|
+
|
409
|
+
check_call(["git", "add", "--all", "CONST_create_template/"])
|
410
|
+
|
411
|
+
def changed_files() -> list[str]:
|
412
|
+
try:
|
413
|
+
status = [
|
414
|
+
[s for s in status.strip().split(" ", maxsplit=1) if s]
|
415
|
+
for status in check_git_status_output().strip().split("\n")
|
416
|
+
if status
|
417
|
+
]
|
418
|
+
return [
|
419
|
+
file.strip().split(" ")[-1]
|
420
|
+
for state, file in status
|
421
|
+
if state == "M" and not file.strip().startswith("CONST_")
|
422
|
+
]
|
423
|
+
except: # pylint: disable=bare-except
|
424
|
+
self.print_step(
|
425
|
+
step,
|
426
|
+
error=True,
|
427
|
+
message=f"Error while getting changed files:\n{check_git_status_output()}",
|
428
|
+
prompt="Fix the error and run the step again:",
|
429
|
+
)
|
430
|
+
sys.exit(1)
|
431
|
+
|
432
|
+
changed_before_style = changed_files()
|
433
|
+
|
434
|
+
fix_style()
|
435
|
+
|
436
|
+
# Revert code style changes in the project otherwise we get an error: does not match index
|
437
|
+
# on git apply.
|
438
|
+
changed_after_style = changed_files()
|
439
|
+
to_checkout = [file for file in changed_after_style if file not in changed_before_style]
|
440
|
+
if to_checkout:
|
441
|
+
subprocess.run(["git", "checkout"] + to_checkout, check=True)
|
442
|
+
|
443
|
+
check_call(["git", "add", "--all", "CONST_create_template/"])
|
444
|
+
check_call(["git", "clean", "-Xf", "CONST_create_template/"])
|
445
|
+
self.run_step(step + 1)
|
446
|
+
|
447
|
+
@Step(5)
|
448
|
+
def step5(self, step: int) -> None:
|
449
|
+
if "managed_files" not in self.project:
|
450
|
+
unmanaged_files = "\n".join(["- " + e for e in self.project.get("unmanaged_files", [])])
|
451
|
+
self.print_step(
|
452
|
+
step,
|
453
|
+
message="In the new version, we will also manage almost all the create template files.\n"
|
454
|
+
"By default, files conforming to the following regex pattern will not be replaced:\n"
|
455
|
+
f"{unmanaged_files} Therefore, you should fill the 'managed_files' in you 'project.yaml' "
|
456
|
+
"file with at least `[]`.",
|
457
|
+
prompt="Fill it and run the step again:",
|
458
|
+
)
|
459
|
+
else:
|
460
|
+
self.run_step(step + 1)
|
461
|
+
|
462
|
+
@Step(6)
|
463
|
+
def step6(self, step: int) -> None:
|
464
|
+
task_to_do = False
|
465
|
+
for upgrade_file in cast(list[dict[str, Any]], self.get_upgrade("upgrade_files")):
|
466
|
+
action = upgrade_file["action"]
|
467
|
+
if action == "remove":
|
468
|
+
task_to_do |= self.files_to_remove(upgrade_file)
|
469
|
+
elif action == "move":
|
470
|
+
task_to_do |= self.files_to_move(upgrade_file)
|
471
|
+
|
472
|
+
if task_to_do:
|
473
|
+
self.print_step(
|
474
|
+
step + 1,
|
475
|
+
message="""Some `managed_files` or `unmanaged_files` should be updated,
|
476
|
+
see message above red and yellow messages to know what should be changed.
|
477
|
+
If there is some false positive you should manually revert the changes and
|
478
|
+
in the (un)managed files replace the pattern by:
|
479
|
+
|
480
|
+
- pattern: <pattern>
|
481
|
+
no_touch: True
|
482
|
+
""",
|
483
|
+
)
|
484
|
+
else:
|
485
|
+
self.run_step(step + 1)
|
486
|
+
|
487
|
+
def files_to_remove(self, element: dict[str, Any], prefix: str = "", force: bool = False) -> bool:
|
488
|
+
task_to_do = False
|
489
|
+
for path in element["paths"]:
|
490
|
+
file_ = os.path.join(prefix, path.format(package=self.project["project_package"]))
|
491
|
+
if os.path.exists(file_):
|
492
|
+
managed = False
|
493
|
+
if not force:
|
494
|
+
for files in self.project["managed_files"]:
|
495
|
+
if isinstance(files, str):
|
496
|
+
pattern = files
|
497
|
+
no_touch = False
|
498
|
+
else:
|
499
|
+
pattern = files["pattern"]
|
500
|
+
no_touch = files.get("no_touch", False)
|
501
|
+
if re.match(pattern + "$", file_):
|
502
|
+
if no_touch:
|
503
|
+
managed = True
|
504
|
+
else:
|
505
|
+
print(
|
506
|
+
colorize(
|
507
|
+
f"The file '{file_}' has been removed but he is in the "
|
508
|
+
f"`managed_files` as '{pattern}'.",
|
509
|
+
Color.RED,
|
510
|
+
)
|
511
|
+
)
|
512
|
+
task_to_do = True
|
513
|
+
for pattern in self.project.get("unmanaged_files", []):
|
514
|
+
if re.match(pattern + "$", file_):
|
515
|
+
print(
|
516
|
+
colorize(
|
517
|
+
f"The file '{file_}' has been removed but he is in the "
|
518
|
+
f"`unmanaged_files` as '{pattern}'.",
|
519
|
+
Color.YELLOW,
|
520
|
+
)
|
521
|
+
)
|
522
|
+
task_to_do = True
|
523
|
+
if not managed:
|
524
|
+
print(f"The file '{file_}' is removed.")
|
525
|
+
if "version" in element and "from" in element:
|
526
|
+
print(
|
527
|
+
f"Was used in version {element['from']}, to be removed from version "
|
528
|
+
f"{element['version']}."
|
529
|
+
)
|
530
|
+
if os.path.isdir(file_):
|
531
|
+
shutil.rmtree(file_)
|
532
|
+
else:
|
533
|
+
os.remove(file_)
|
534
|
+
return task_to_do
|
535
|
+
|
536
|
+
def files_to_move(self, element: dict[str, Any], prefix: str = "", force: bool = False) -> bool:
|
537
|
+
task_to_do = False
|
538
|
+
src = os.path.join(prefix, element["from"].format(package=self.project["project_package"]))
|
539
|
+
dst = os.path.join(prefix, element["to"].format(package=self.project["project_package"]))
|
540
|
+
if os.path.exists(src):
|
541
|
+
managed = False
|
542
|
+
type_ = "directory" if os.path.isdir(src) else "file"
|
543
|
+
if not force:
|
544
|
+
for files in self.project["managed_files"]:
|
545
|
+
if isinstance(files, str):
|
546
|
+
pattern = files
|
547
|
+
no_touch = False
|
548
|
+
else:
|
549
|
+
pattern = files["pattern"]
|
550
|
+
no_touch = files.get("no_touch", False)
|
551
|
+
if re.match(pattern + "$", src):
|
552
|
+
if no_touch:
|
553
|
+
managed = True
|
554
|
+
else:
|
555
|
+
print(
|
556
|
+
colorize(
|
557
|
+
f"The {type_} '{src}' is present in the `managed_files` as '{pattern}', "
|
558
|
+
f"but it has been moved to '{dst}'.",
|
559
|
+
Color.RED,
|
560
|
+
)
|
561
|
+
)
|
562
|
+
task_to_do = True
|
563
|
+
if re.match(pattern + "$", dst):
|
564
|
+
print(
|
565
|
+
colorize(
|
566
|
+
f"The {type_} '{dst}' is present in the `managed_files` as '{pattern}', "
|
567
|
+
f"but a file have been moved on it from '{src}'.",
|
568
|
+
Color.RED,
|
569
|
+
)
|
570
|
+
)
|
571
|
+
task_to_do = True
|
572
|
+
for pattern in self.project["unmanaged_files"]:
|
573
|
+
if re.match(pattern + "$", src):
|
574
|
+
print(
|
575
|
+
colorize(
|
576
|
+
f"The {type_} '{src}' is present in the `unmanaged_files` as '{pattern}', "
|
577
|
+
f"but it has been moved to '{dst}'.",
|
578
|
+
Color.YELLOW,
|
579
|
+
)
|
580
|
+
)
|
581
|
+
task_to_do = True
|
582
|
+
if re.match(pattern + "$", dst):
|
583
|
+
print(
|
584
|
+
colorize(
|
585
|
+
f"The {type_} '{dst}' is present in the `unmanaged_files` as '{pattern}', "
|
586
|
+
f"but a file have been moved on it from '{src}'.",
|
587
|
+
Color.YELLOW,
|
588
|
+
)
|
589
|
+
)
|
590
|
+
task_to_do = True
|
591
|
+
if not managed and os.path.exists(dst) and not element.get("override", False):
|
592
|
+
print(colorize(f"The destination '{dst}' already exists, ignoring.", Color.YELLOW))
|
593
|
+
elif not managed:
|
594
|
+
print(f"Move the {type_} '{src}' to '{dst}'.")
|
595
|
+
if "version" in element:
|
596
|
+
print(f"Needed from version {element['version']}.")
|
597
|
+
if os.path.dirname(dst) != "":
|
598
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
599
|
+
try:
|
600
|
+
check_call(["git", "mv", src, dst])
|
601
|
+
except Exception as exception: # pylint: disable=broad-exception-caught
|
602
|
+
print(f"[Warning] Git move error: {exception}.")
|
603
|
+
os.rename(src, dst)
|
604
|
+
return task_to_do
|
605
|
+
|
606
|
+
@Step(7)
|
607
|
+
def step7(self, step: int) -> None:
|
608
|
+
self.files_to_get(step)
|
609
|
+
self.run_step(step + 1)
|
610
|
+
|
611
|
+
def is_managed(self, file_: str, files_to_get: bool = False) -> bool:
|
612
|
+
# Dictionary with:
|
613
|
+
# include: list of include regular expression
|
614
|
+
# exclude: list of exclude regular expression
|
615
|
+
default_project_file = cast(dict[str, list[str]], self.get_upgrade("default_project_file"))
|
616
|
+
|
617
|
+
# Managed means managed by the application owner, not the c2cupgrade
|
618
|
+
managed = False
|
619
|
+
if (
|
620
|
+
not files_to_get
|
621
|
+
or os.path.exists(file_)
|
622
|
+
or not check_git_status_output(["CONST_create_template/" + file_]).startswith("A ")
|
623
|
+
):
|
624
|
+
for pattern in default_project_file["include"]:
|
625
|
+
if re.match(pattern + "$", file_):
|
626
|
+
print(f"File '{file_}' included by migration config pattern '{pattern}'.")
|
627
|
+
managed = True
|
628
|
+
break
|
629
|
+
if managed:
|
630
|
+
for pattern in default_project_file["exclude"]:
|
631
|
+
if re.match(pattern + "$", file_):
|
632
|
+
print(f"File '{file_}' excluded by migration config pattern '{pattern}'.")
|
633
|
+
print("managed", file_, pattern)
|
634
|
+
managed = False
|
635
|
+
break
|
636
|
+
else:
|
637
|
+
print(f"New file '{file_}'.")
|
638
|
+
|
639
|
+
if not managed and not os.path.exists(file_):
|
640
|
+
for pattern in self.get_upgrade("extra"):
|
641
|
+
if re.match(pattern + "$", file_):
|
642
|
+
print(f"File '{file_}' is an extra by migration config pattern '{pattern}'.")
|
643
|
+
managed = True
|
644
|
+
|
645
|
+
if not managed:
|
646
|
+
for files in self.project["managed_files"]:
|
647
|
+
if isinstance(files, str):
|
648
|
+
pattern = files
|
649
|
+
else:
|
650
|
+
pattern = files["pattern"]
|
651
|
+
if re.match(pattern + "$", file_):
|
652
|
+
print(f"File '{file_}' included by project config pattern `managed_files` '{pattern}'.")
|
653
|
+
print("managed", file_, pattern)
|
654
|
+
managed = True
|
655
|
+
break
|
656
|
+
if managed:
|
657
|
+
for pattern in self.project.get("unmanaged_files", []):
|
658
|
+
if re.match(pattern + "$", file_):
|
659
|
+
print(f"File '{file_}' excluded by project config pattern `unmanaged_files` '{pattern}'.")
|
660
|
+
managed = False
|
661
|
+
break
|
662
|
+
|
663
|
+
return managed
|
664
|
+
|
665
|
+
def files_to_get(self, step: int, pre: bool = False) -> bool:
|
666
|
+
error = False
|
667
|
+
for root, _, files in os.walk("CONST_create_template"):
|
668
|
+
root = root[len("CONST_create_template/") :]
|
669
|
+
for file_ in files:
|
670
|
+
destination = os.path.join(root, file_)
|
671
|
+
managed = self.is_managed(destination, True)
|
672
|
+
source = os.path.join("CONST_create_template", destination)
|
673
|
+
if not managed and (not os.path.exists(destination) or not filecmp.cmp(source, destination)):
|
674
|
+
print(colorize(f"Get the file '{destination}' from the create template.", Color.GREEN))
|
675
|
+
if not pre:
|
676
|
+
if os.path.dirname(destination) != "":
|
677
|
+
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
678
|
+
try:
|
679
|
+
shutil.copyfile(source, destination)
|
680
|
+
shutil.copymode(source, destination)
|
681
|
+
except PermissionError as exception:
|
682
|
+
self.print_step(
|
683
|
+
step,
|
684
|
+
error=True,
|
685
|
+
message=(
|
686
|
+
"All your project files should be owned by your user, "
|
687
|
+
"current error:\n" + str(exception)
|
688
|
+
),
|
689
|
+
prompt="Fix it and run the upgrade again:",
|
690
|
+
)
|
691
|
+
sys.exit(1)
|
692
|
+
elif managed:
|
693
|
+
print(f"The file '{destination}' is managed by the project.")
|
694
|
+
elif os.path.exists(destination) and filecmp.cmp(source, destination):
|
695
|
+
print(f"The file '{destination}' does not change.")
|
696
|
+
else:
|
697
|
+
print(f"Unknown stat for the file '{destination}'.")
|
698
|
+
sys.exit(2)
|
699
|
+
return error
|
700
|
+
|
701
|
+
@Step(8)
|
702
|
+
def step8(self, step: int) -> None:
|
703
|
+
with open("changelog.diff", "w", encoding="utf8") as diff_file:
|
704
|
+
check_call(["git", "diff", "--", "CONST_CHANGELOG.txt"], stdout=diff_file)
|
705
|
+
|
706
|
+
from210 = False
|
707
|
+
try:
|
708
|
+
check_call(["grep", "--", "-Version 2.1.0", "changelog.diff"])
|
709
|
+
from210 = True
|
710
|
+
except subprocess.CalledProcessError:
|
711
|
+
pass
|
712
|
+
if from210:
|
713
|
+
check_call(["cp", "CONST_CHANGELOG.txt", "changelog.diff"])
|
714
|
+
|
715
|
+
if os.path.getsize("changelog.diff") == 0:
|
716
|
+
self.run_step(step + 1)
|
717
|
+
else:
|
718
|
+
self.print_step(
|
719
|
+
step + 1,
|
720
|
+
message="Apply the manual migration steps based on what is in the CONST_CHANGELOG.txt "
|
721
|
+
"file (listed in the `changelog.diff` file).",
|
722
|
+
)
|
723
|
+
|
724
|
+
def get_modified(self, status_path: str) -> list[str]:
|
725
|
+
status = check_git_status_output([status_path]).split("\n")
|
726
|
+
status = [s for s in status if len(s) > 3]
|
727
|
+
status = [s[3:] for s in status if s[:3].strip() == "M"]
|
728
|
+
for pattern in self.get_upgrade("no_diff"):
|
729
|
+
matcher = re.compile(f"CONST_create_template/{pattern}$")
|
730
|
+
status = [s for s in status if not matcher.match(s)]
|
731
|
+
status = [s for s in status if os.path.exists(s[len("CONST_create_template/") :])]
|
732
|
+
status = [s for s in status if not filecmp.cmp(s, s[len("CONST_create_template/") :])]
|
733
|
+
return status
|
734
|
+
|
735
|
+
@Step(9)
|
736
|
+
def step9(self, step: int) -> None:
|
737
|
+
if os.path.isfile("changelog.diff"):
|
738
|
+
os.unlink("changelog.diff")
|
739
|
+
|
740
|
+
status = self.get_modified(
|
741
|
+
f"CONST_create_template/geoportal/{self.project['project_package']}_geoportal/static-ngeo"
|
742
|
+
)
|
743
|
+
status += ["CONST_create_template/geoportal/vars.yaml"]
|
744
|
+
|
745
|
+
with open("ngeo.diff", "w", encoding="utf8") as diff_file:
|
746
|
+
if status:
|
747
|
+
check_call(
|
748
|
+
["git", "diff", "--relative=CONST_create_template", "--staged", "--"] + status,
|
749
|
+
stdout=diff_file,
|
750
|
+
)
|
751
|
+
|
752
|
+
if os.path.getsize("ngeo.diff") == 0:
|
753
|
+
self.run_step(step + 1)
|
754
|
+
else:
|
755
|
+
self.print_step(
|
756
|
+
step + 1,
|
757
|
+
message="Manually apply the ngeo application changes as shown in the `ngeo.diff` file.\n"
|
758
|
+
+ DIFF_NOTICE
|
759
|
+
+ "\nNote that you can also apply them using: git apply --3way ngeo.diff",
|
760
|
+
)
|
761
|
+
|
762
|
+
@Step(10)
|
763
|
+
def step10(self, step: int) -> None:
|
764
|
+
if os.path.isfile("ngeo.diff"):
|
765
|
+
os.unlink("ngeo.diff")
|
766
|
+
|
767
|
+
status = self.get_modified("CONST_create_template")
|
768
|
+
status = [
|
769
|
+
s
|
770
|
+
for s in status
|
771
|
+
if not s.startswith(
|
772
|
+
f"CONST_create_template/geoportal/{self.project['project_package']}_geoportal/static-ngeo/"
|
773
|
+
)
|
774
|
+
]
|
775
|
+
status = [s for s in status if s != "CONST_create_template/geoportal/vars.yaml"]
|
776
|
+
|
777
|
+
if status:
|
778
|
+
with open("create.diff", "w", encoding="utf8") as diff_file:
|
779
|
+
if status:
|
780
|
+
check_call(
|
781
|
+
["git", "diff", "--relative=CONST_create_template", "--staged", "--"] + status,
|
782
|
+
stdout=diff_file,
|
783
|
+
)
|
784
|
+
|
785
|
+
if os.path.getsize("create.diff") == 0:
|
786
|
+
self.run_step(step + 1)
|
787
|
+
else:
|
788
|
+
self.print_step(
|
789
|
+
step + 1,
|
790
|
+
message="The `create.diff` file is a recommendation of the changes that you "
|
791
|
+
"should apply to your project.\n"
|
792
|
+
+ DIFF_NOTICE
|
793
|
+
+ "\nNote that you can also apply them using: git apply --3way create.diff",
|
794
|
+
)
|
795
|
+
else:
|
796
|
+
self.run_step(step + 1)
|
797
|
+
|
798
|
+
@Step(11)
|
799
|
+
def step11(self, step: int) -> None:
|
800
|
+
if os.path.isfile("create.diff"):
|
801
|
+
os.unlink("create.diff")
|
802
|
+
|
803
|
+
fix_style()
|
804
|
+
|
805
|
+
message = [
|
806
|
+
"The upgrade is nearly done, now you should:",
|
807
|
+
"- Build your application with `./upgrade --finalize [build arguments]`",
|
808
|
+
f"- Test your application on '{self.project.get('application_url', '... missing ...')}'.",
|
809
|
+
]
|
810
|
+
|
811
|
+
if os.path.isfile(".upgrade.yaml"):
|
812
|
+
os.unlink(".upgrade.yaml")
|
813
|
+
with open(".UPGRADE_SUCCESS", "w", encoding="utf8"):
|
814
|
+
pass
|
815
|
+
self.print_step(step + 1, message="\n".join(message))
|
816
|
+
|
817
|
+
@Step(12, file_marker=False)
|
818
|
+
def step12(self, step: int) -> None:
|
819
|
+
if os.path.isfile(".UPGRADE_SUCCESS"):
|
820
|
+
os.unlink(".UPGRADE_SUCCESS")
|
821
|
+
good, message = self.test_checkers()
|
822
|
+
if good:
|
823
|
+
self.run_step(step + 1)
|
824
|
+
else:
|
825
|
+
self.print_step(
|
826
|
+
step,
|
827
|
+
error=True,
|
828
|
+
message=message,
|
829
|
+
prompt="Correct the checker, then run the step again "
|
830
|
+
"(If you want to fix it later you can pass to the next step:int):",
|
831
|
+
)
|
832
|
+
sys.exit(1)
|
833
|
+
|
834
|
+
@Step(13, file_marker=False)
|
835
|
+
def step13(self, step: int) -> None:
|
836
|
+
# Required to remove from the Git stage the ignored file when we lunch the step again
|
837
|
+
check_call(["git", "reset", "--mixed"])
|
838
|
+
|
839
|
+
check_call(["git", "add", "--all"])
|
840
|
+
check_call(["git", "status"])
|
841
|
+
|
842
|
+
self.print_step(
|
843
|
+
step + 1,
|
844
|
+
message="We will commit all the above files!\n"
|
845
|
+
"If there are some files which should not be committed, then you should "
|
846
|
+
f"add them into the `.gitignore` file and launch upgrade {step} again.",
|
847
|
+
prompt="Then to commit your changes type:",
|
848
|
+
)
|
849
|
+
|
850
|
+
@Step(14, file_marker=False)
|
851
|
+
def step14(self, _: int) -> None:
|
852
|
+
if os.path.isfile(".UPGRADE_INSTRUCTIONS"):
|
853
|
+
os.unlink(".UPGRADE_INSTRUCTIONS")
|
854
|
+
check_call(
|
855
|
+
[
|
856
|
+
"git",
|
857
|
+
"commit",
|
858
|
+
"--message=Upgrade to GeoMapFish "
|
859
|
+
f"{pkg_resources.get_distribution('c2cgeoportal_commons').version}",
|
860
|
+
]
|
861
|
+
)
|
862
|
+
|
863
|
+
print("")
|
864
|
+
print(self.color_bar)
|
865
|
+
print("")
|
866
|
+
print(colorize("Congratulations, your upgrade was successful.", Color.GREEN))
|
867
|
+
print("")
|
868
|
+
branch = check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip()
|
869
|
+
print("Now all your files are committed; you should do a git push:")
|
870
|
+
print(f"git push {self.options.git_remote} {branch}.")
|
871
|
+
|
872
|
+
|
873
|
+
def check_git_status_output(args: list[str] | None = None) -> str:
|
874
|
+
"""Check if there is something that's not committed."""
|
875
|
+
return check_output(["git", "status", "--short"] + (args if args is not None else [])).decode("utf-8")
|
876
|
+
|
877
|
+
|
878
|
+
if __name__ == "__main__":
|
879
|
+
main()
|