Skip to content

Commit 9fb4cf4

Browse files
authored
Allow usage of environment variables in rules path (#608)
2 parents d38e209 + c4f5a0c commit 9fb4cf4

File tree

7 files changed

+115
-3
lines changed

7 files changed

+115
-3
lines changed

docs/reference/qdt_profile.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,39 @@ You can add rules to make the profile deployment conditional. In the following e
3737

3838
The rules engine is based on [Python Rule Engine](https://github.com/santalvarez/python-rule-engine/) project whom rules syntax belongs to [JSON Rules Engine](https://github.com/CacheControl/json-rules-engine).
3939

40+
> Added in version 0.38
41+
42+
You can also deploy profiles based on environment variables. In the following example, the profile will be deployed only if `QDT_IS_GIS_ADMIN` exists:
43+
44+
```json
45+
{
46+
"$schema": "https://raw.githubusercontent.com/qgis-deployment/qgis-deployment-toolbelt-cli/main/docs/schemas/profile/qgis_profile.json",
47+
"name": "only_gis_admin",
48+
"folder_name": "qdt_only_gis_admin",
49+
"description": "A QGIS profile for QDT with a conditional deployment rule for GIS admin",
50+
"author": "Julien Moura",
51+
"email": "[email protected]",
52+
"qgisMinimumVersion": "3.34.0",
53+
"qgisMaximumVersion": "3.99.10",
54+
"version": "1.7.0",
55+
"rules": [
56+
{
57+
"name": "QDT_IS_GIS_ADMIN exists",
58+
"description": "Deploy only if $env:QDT_IS_GIS_ADMIN exists",
59+
"conditions": {
60+
"all": [
61+
{
62+
"path": "$.env.QDT_IS_GIS_ADMIN",
63+
"operator": "not_equal",
64+
"value": ""
65+
}
66+
]
67+
}
68+
}
69+
]
70+
}
71+
```
72+
4073
### Conditions and rules context
4174

4275
Rules is a set of conditions that use logical operators to compare values with context (a set of facts) which is exposed as a JSON object. Here comes the context for a Linux environment:

docs/schemas/scenario/settings.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@
8181
}
8282
}
8383
},
84+
"RULES_ONLY_PREFIXED_VARIABLES": {
85+
"default": true,
86+
"description": "If set to `true` (default), only environment variables prefixed with prefixes listed in RULES_VARIABLES_PREFIX are considered in rules for security concerns. If set to `false`, all environment variables are considered in rules.",
87+
"title": "Only prefixed variables",
88+
"type": "boolean"
89+
},
90+
"RULES_VARIABLES_PREFIX": {
91+
"default": [
92+
"QDT_,QGIS_"
93+
],
94+
"description": "List of prefixes of environment variables considered in rules. Only relevant if RULES_ONLY_PREFIXED_VARIABLES is set to `true`.",
95+
"title": "Variables prefixes",
96+
"type": "array",
97+
"items": {
98+
"type": "string"
99+
}
100+
},
84101
"SCENARIO_VALIDATION": {
85102
"default": false,
86103
"description": "Enable scenario validation. This will check the scenario against the JSON schema.",

qgis_deployment_toolbelt/commands/deployment.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,15 @@ def run(args: argparse.Namespace):
188188
)
189189
envvar_name = envvar_name.removeprefix("QDT_")
190190

191-
environ[f"QDT_{envvar_name}"] = str(envvar_value)
191+
prefixed_envvar_name = f"QDT_{envvar_name}"
192+
193+
if prefixed_envvar_name in environ:
194+
logger.warning(
195+
f"Environment variable {prefixed_envvar_name} already set. "
196+
"Overwriting it with new value."
197+
)
198+
199+
environ[prefixed_envvar_name] = str(envvar_value)
192200
else:
193201
logger.debug(f"Ignored None value: {envvar_name}.")
194202
else:

qgis_deployment_toolbelt/jobs/generic_job.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from qgis_deployment_toolbelt.profiles.qdt_profile import QdtProfile
3434
from qgis_deployment_toolbelt.profiles.rules_context import QdtRulesContext
35+
from qgis_deployment_toolbelt.utils.str2bool import str2bool
3536

3637
# #############################################################################
3738
# ########## Globals ###############
@@ -55,7 +56,16 @@ def __init__(self) -> None:
5556
"""Object instanciation."""
5657
# operating system configuration
5758
self.os_config = OSConfiguration.from_opersys()
58-
self.qdt_rules_context = QdtRulesContext()
59+
60+
# QDT rules context
61+
only_prefixed_variables = str2bool(
62+
getenv("QDT_RULES_ONLY_PREFIXED_VARIABLES", "true")
63+
)
64+
variables_prefix = getenv("QDT_RULES_VARIABLES_PREFIX", "QDT_,QGIS_").split(",")
65+
self.qdt_rules_context = QdtRulesContext(
66+
only_prefixed_variables=only_prefixed_variables,
67+
variables_prefix=variables_prefix,
68+
)
5969

6070
# local QDT folders
6171
self.qdt_working_folder = get_qdt_working_directory()

qgis_deployment_toolbelt/profiles/rules_context.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import platform
1818
from datetime import date
1919
from getpass import getuser
20+
from os import _Environ, environ
2021
from sys import platform as opersys
2122

2223
# package
@@ -43,6 +44,25 @@
4344

4445

4546
class QdtRulesContext:
47+
def __init__(
48+
self,
49+
only_prefixed_variables: bool = True,
50+
variables_prefix: list[str] | None = None,
51+
) -> None:
52+
"""Initialize a QDT rules context object.
53+
54+
Args:
55+
only_prefixed_variables (bool, optional): Option to only list prefixed
56+
variables. Defaults to True.
57+
variables_prefix (list[str] | None, optional): List of allowed prefixes.
58+
Defaults to None.
59+
"""
60+
self.only_prefixed_variables = only_prefixed_variables
61+
62+
if variables_prefix is None:
63+
self.variables_prefix = ["QDT_", "QGIS_"]
64+
else:
65+
self.variables_prefix = variables_prefix
4666

4767
@property
4868
def _context_date(self) -> dict:
@@ -60,6 +80,29 @@ def _context_date(self) -> dict:
6080
"current_year": today.year,
6181
}
6282

83+
@property
84+
def _context_env(self) -> dict[str, str] | _Environ[str]:
85+
"""Returns a dictionary containing environment variables that can be used in
86+
QDT various places: rules...
87+
88+
The environment variables can be filtered based on self.only_prefixed_variables
89+
and self.variables_prefix settings.
90+
91+
Returns:
92+
dict[str, str] | _Environ[str]: dict with environment variables to use in
93+
rules
94+
"""
95+
96+
if self.only_prefixed_variables:
97+
# Filter variables that start with any of the prefixes
98+
return {
99+
key: value
100+
for key, value in environ.items()
101+
if any(key.startswith(prefix) for prefix in self.variables_prefix)
102+
}
103+
104+
return environ
105+
63106
@property
64107
def _context_environment(self) -> dict:
65108
"""Returns a dictionary containing some environment information (computer, network,

qgis_deployment_toolbelt/utils/str2bool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def str2bool(input_var: bool | str, raise_exc: bool = False) -> bool | None:
2929
3030
Args:
3131
input_var (str): value to convert
32-
raise_exc (bool, optional): strict mode, defaults to False. Defaults to False.
32+
raise_exc (bool, optional): strict mode, defaults to False.
3333
3434
Raises:
3535
ValueError: input_var is not in true and false sets

tests/test_rules_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def test_rules_export_to_json(self):
4848

4949
self.assertIsInstance(context_data, dict)
5050
self.assertIn("date", context_data)
51+
self.assertIn("env", context_data)
5152
self.assertIn("environment", context_data)
5253
self.assertIn("user", context_data)
5354

0 commit comments

Comments
 (0)