opentf-toolkit-nightly 0.63.0.dev1385__py3-none-any.whl → 0.63.0.dev1397__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.
- opentf/toolkit/channels.py +28 -15
- opentf/toolkit/models.py +208 -0
- {opentf_toolkit_nightly-0.63.0.dev1385.dist-info → opentf_toolkit_nightly-0.63.0.dev1397.dist-info}/METADATA +1 -1
- {opentf_toolkit_nightly-0.63.0.dev1385.dist-info → opentf_toolkit_nightly-0.63.0.dev1397.dist-info}/RECORD +7 -6
- {opentf_toolkit_nightly-0.63.0.dev1385.dist-info → opentf_toolkit_nightly-0.63.0.dev1397.dist-info}/WHEEL +1 -1
- {opentf_toolkit_nightly-0.63.0.dev1385.dist-info → opentf_toolkit_nightly-0.63.0.dev1397.dist-info}/licenses/LICENSE +0 -0
- {opentf_toolkit_nightly-0.63.0.dev1385.dist-info → opentf_toolkit_nightly-0.63.0.dev1397.dist-info}/top_level.txt +0 -0
opentf/toolkit/channels.py
CHANGED
|
@@ -210,26 +210,31 @@ def _add_default_variables(
|
|
|
210
210
|
script.append(VARIABLE_MAKER[runner_os]('CI', 'true'))
|
|
211
211
|
|
|
212
212
|
|
|
213
|
-
def
|
|
213
|
+
def get_opentf_variables_path(metadata: Dict[str, Any]) -> str:
|
|
214
214
|
return VARIABLES_TEMPLATE[metadata['channel_os']].format(
|
|
215
215
|
job_id=metadata['job_id'], root=metadata['channel_temp']
|
|
216
216
|
)
|
|
217
217
|
|
|
218
218
|
|
|
219
|
-
def
|
|
219
|
+
def _read_opentf_variables(lines: List[str]) -> Dict[str, str]:
|
|
220
220
|
variables = {}
|
|
221
|
+
for line in lines:
|
|
222
|
+
if '=' not in line:
|
|
223
|
+
continue
|
|
224
|
+
line = line.strip()
|
|
225
|
+
if set_export := OPENTF_VARIABLES_REGEX.match(line):
|
|
226
|
+
line = set_export.group(2)
|
|
227
|
+
if line.startswith('"'):
|
|
228
|
+
line = line[1:-1]
|
|
229
|
+
key, _, value = line.partition('=')
|
|
230
|
+
if OPENTF_VARIABLES_NAME_REGEX.match(key):
|
|
231
|
+
variables[key] = value
|
|
232
|
+
return variables
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _get_opentf_variables(path: str) -> Dict[str, str]:
|
|
221
236
|
with open(path, 'r') as f:
|
|
222
|
-
|
|
223
|
-
if '=' not in line:
|
|
224
|
-
continue
|
|
225
|
-
line = line.strip()
|
|
226
|
-
if set_export := OPENTF_VARIABLES_REGEX.match(line):
|
|
227
|
-
line = set_export.group(2)
|
|
228
|
-
if line.startswith('"'):
|
|
229
|
-
line = line[1:-1]
|
|
230
|
-
key, _, value = line.partition('=')
|
|
231
|
-
if OPENTF_VARIABLES_NAME_REGEX.match(key):
|
|
232
|
-
variables[key] = value
|
|
237
|
+
variables = _read_opentf_variables(f.readlines())
|
|
233
238
|
try:
|
|
234
239
|
os.remove(path)
|
|
235
240
|
except FileNotFoundError:
|
|
@@ -428,6 +433,7 @@ def process_output(
|
|
|
428
433
|
jobstate: JobState,
|
|
429
434
|
_get: Callable[[str, str], None],
|
|
430
435
|
_put: Callable[[str, str], None],
|
|
436
|
+
variables: Optional[List[str]] = None,
|
|
431
437
|
) -> Dict[str, Any]:
|
|
432
438
|
"""Process output, filling structures.
|
|
433
439
|
|
|
@@ -566,8 +572,14 @@ def process_output(
|
|
|
566
572
|
if metadata.get('artifacts'):
|
|
567
573
|
del metadata['artifacts']
|
|
568
574
|
|
|
575
|
+
opentf_variables = None
|
|
569
576
|
if metadata['step_sequence_id'] != CHANNEL_RELEASE:
|
|
570
|
-
|
|
577
|
+
if variables:
|
|
578
|
+
opentf_variables = _read_opentf_variables(variables)
|
|
579
|
+
else:
|
|
580
|
+
_attach(
|
|
581
|
+
get_opentf_variables_path(metadata), f'type={OPENTF_VARIABLES_TYPE}'
|
|
582
|
+
)
|
|
571
583
|
|
|
572
584
|
result = make_event(EXECUTIONRESULT, metadata=metadata, status=resp)
|
|
573
585
|
if outputs:
|
|
@@ -579,7 +591,8 @@ def process_output(
|
|
|
579
591
|
result['metadata']['attachments'] = attachments_metadata
|
|
580
592
|
if has_artifacts:
|
|
581
593
|
result['metadata']['upload'] = resp
|
|
582
|
-
|
|
594
|
+
if opentf_variables:
|
|
595
|
+
result['variables'] = opentf_variables
|
|
583
596
|
return result
|
|
584
597
|
|
|
585
598
|
|
opentf/toolkit/models.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Copyright (c) 2025 Henix, Henix.fr
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Handling models from plugins configuration files."""
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
|
|
21
|
+
from opentf.commons import read_and_validate
|
|
22
|
+
from opentf.toolkit import watch_file
|
|
23
|
+
|
|
24
|
+
Model = dict[str, Any]
|
|
25
|
+
Spec = dict[str, Any]
|
|
26
|
+
|
|
27
|
+
########################################################################
|
|
28
|
+
|
|
29
|
+
IMG_MODELS = []
|
|
30
|
+
|
|
31
|
+
########################################################################
|
|
32
|
+
### Configuration loader helpers
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def deduplicate(
|
|
36
|
+
plugin,
|
|
37
|
+
models: list[Model],
|
|
38
|
+
) -> tuple[list[Model], set[str]]:
|
|
39
|
+
"""Deduplicate models in a list.
|
|
40
|
+
|
|
41
|
+
# Required parameter
|
|
42
|
+
|
|
43
|
+
- models: a list of dictionaries (models), in increasing priority order.
|
|
44
|
+
|
|
45
|
+
# Returned value
|
|
46
|
+
|
|
47
|
+
A tuple containing a list of deduplicated models and a possibly empty
|
|
48
|
+
list of warnings.
|
|
49
|
+
"""
|
|
50
|
+
seen = {}
|
|
51
|
+
name, kind = None, None
|
|
52
|
+
warnings = set()
|
|
53
|
+
for model in reversed(models):
|
|
54
|
+
key = (name, kind) = model.get('name'), model.get('kind')
|
|
55
|
+
if key not in seen:
|
|
56
|
+
seen[key] = model
|
|
57
|
+
else:
|
|
58
|
+
if model.get('.source') != 'default':
|
|
59
|
+
msg = f'Duplicate definitions found for {plugin.name} {kind+' ' if kind else ''}"{name}", only the definition with the highest priority will be used.'
|
|
60
|
+
warnings.add(msg)
|
|
61
|
+
if warnings:
|
|
62
|
+
for msg in warnings:
|
|
63
|
+
plugin.logger.warning(msg)
|
|
64
|
+
return list(reversed(list(seen.values()))), warnings
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def filter_listdir(plugin, path: str, kinds: tuple[str, ...]) -> list[str]:
|
|
68
|
+
"""listdir-like, filtering for files with specified extensions."""
|
|
69
|
+
files = [
|
|
70
|
+
f
|
|
71
|
+
for f in os.listdir(path)
|
|
72
|
+
if os.path.isfile(os.path.join(path, f)) and f.endswith(kinds)
|
|
73
|
+
]
|
|
74
|
+
if not files:
|
|
75
|
+
plugin.logger.debug('No %s files provided in %s.', ', '.join(kinds), path)
|
|
76
|
+
return sorted(files)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _read_models(
|
|
80
|
+
plugin, schema: str, configfile: str, config_key: str
|
|
81
|
+
) -> list[Model] | None:
|
|
82
|
+
"""Read plugin models JSON or YAML and return models list."""
|
|
83
|
+
try:
|
|
84
|
+
models = read_and_validate(schema, configfile)
|
|
85
|
+
except ValueError as err:
|
|
86
|
+
plugin.logger.error(
|
|
87
|
+
'Invalid %s definition file "%s": %s. Ignoring.',
|
|
88
|
+
plugin.name,
|
|
89
|
+
configfile,
|
|
90
|
+
str(err),
|
|
91
|
+
)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
return models[config_key]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _load_image_models(
|
|
98
|
+
plugin, config_path: str, config_key: str, schema: str, default_models: list[Model]
|
|
99
|
+
) -> list[dict[str, Any]]:
|
|
100
|
+
"""Load models from `CONFIG_PATH` directory.
|
|
101
|
+
|
|
102
|
+
Storing models and possible warnings in plugin.config['CONFIG'].
|
|
103
|
+
"""
|
|
104
|
+
models = default_models
|
|
105
|
+
for config_file in filter_listdir(plugin, config_path, ('.yaml', '.yml')):
|
|
106
|
+
filepath = os.path.join(config_path, config_file)
|
|
107
|
+
try:
|
|
108
|
+
if not (img_models := _read_models(plugin, schema, filepath, config_key)):
|
|
109
|
+
continue
|
|
110
|
+
plugin.logger.debug(
|
|
111
|
+
'Loading %s models from file "%s".', plugin.name, config_file
|
|
112
|
+
)
|
|
113
|
+
models.extend(img_models)
|
|
114
|
+
except Exception as err:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f'Failed to load {plugin.name} models from file "{config_file}": {str(err)}.'
|
|
117
|
+
)
|
|
118
|
+
models, warnings = deduplicate(plugin, models)
|
|
119
|
+
plugin.config['CONFIG'][config_key] = models
|
|
120
|
+
plugin.config['CONFIG']['warnings'] = warnings
|
|
121
|
+
return models
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _refresh_configuration(
|
|
125
|
+
_, configfile: str, schema: str, plugin, config_key: str
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Read plugin models from environment variable specified file.
|
|
128
|
+
|
|
129
|
+
Storing models in .config['CONFIG'], using the following entries:
|
|
130
|
+
|
|
131
|
+
- {config_key}: a list of models
|
|
132
|
+
- warnings: a list of duplicate models warnings
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
config = plugin.config['CONFIG']
|
|
136
|
+
models = IMG_MODELS.copy()
|
|
137
|
+
plugin.logger.info(
|
|
138
|
+
f'Reading {plugin.name} models definition from {configfile}.'
|
|
139
|
+
)
|
|
140
|
+
env_models = _read_models(plugin, schema, configfile, config_key) or []
|
|
141
|
+
models.extend(env_models)
|
|
142
|
+
config[config_key], config['warnings'] = deduplicate(plugin, models)
|
|
143
|
+
except Exception as err:
|
|
144
|
+
plugin.logger.error(
|
|
145
|
+
'Error while reading %s "%s" definition: %s.',
|
|
146
|
+
plugin.name,
|
|
147
|
+
configfile,
|
|
148
|
+
str(err),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
########################################################################
|
|
153
|
+
### Configuration loader
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def load_and_watch_models(
|
|
157
|
+
plugin,
|
|
158
|
+
config_path: str,
|
|
159
|
+
config_key: str,
|
|
160
|
+
schema: str,
|
|
161
|
+
default_models: list[Model],
|
|
162
|
+
env_var: str,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Load plugin configuration models.
|
|
165
|
+
|
|
166
|
+
Plugin configuration models are loaded from configuration files path
|
|
167
|
+
and filepath specified by the environment variable. File specified by the
|
|
168
|
+
environment variable is watched for modifications. Models list is stored
|
|
169
|
+
in `plugin.config['CONFIG'][{config_key}]` entry.
|
|
170
|
+
|
|
171
|
+
# Required parameters
|
|
172
|
+
|
|
173
|
+
- plugin: a Flask plugin
|
|
174
|
+
- config_path: a string, configuration models path, should be a directory
|
|
175
|
+
- config_key: a string, plugin configuration key name
|
|
176
|
+
- schema: a string, plugin models validation schema
|
|
177
|
+
- default_models: a list of plugin-specific default models
|
|
178
|
+
- env_var: a string, environment variable name
|
|
179
|
+
|
|
180
|
+
# Raised exception
|
|
181
|
+
|
|
182
|
+
ValueError is raised if configuration files path is not found or
|
|
183
|
+
is not a directory.
|
|
184
|
+
"""
|
|
185
|
+
if not os.path.isdir(config_path):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
f'Configuration files path "{config_path}" not found or not a directory.'
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
IMG_MODELS.extend(
|
|
191
|
+
_load_image_models(plugin, config_path, config_key, schema, default_models)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if os.environ.get(env_var):
|
|
195
|
+
watch_file(
|
|
196
|
+
plugin,
|
|
197
|
+
os.environ[env_var],
|
|
198
|
+
_refresh_configuration,
|
|
199
|
+
schema,
|
|
200
|
+
plugin,
|
|
201
|
+
config_key,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
plugin.logger.info(
|
|
205
|
+
'Loading default %s definitions and definitions from "%s".',
|
|
206
|
+
plugin.name,
|
|
207
|
+
config_path,
|
|
208
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opentf-toolkit-nightly
|
|
3
|
-
Version: 0.63.0.
|
|
3
|
+
Version: 0.63.0.dev1397
|
|
4
4
|
Summary: OpenTestFactory Orchestrator Toolkit
|
|
5
5
|
Home-page: https://gitlab.com/henixdevelopment/open-source/opentestfactory/python-toolkit
|
|
6
6
|
Author: Martin Lafaix
|
|
@@ -58,10 +58,11 @@ opentf/schemas/opentestfactory.org/v1beta2/ServiceConfig.json,sha256=rEvK2YWL5lG
|
|
|
58
58
|
opentf/scripts/launch_java_service.sh,sha256=S0jAaCuv2sZy0Gf2NGBuPX-eD531rcM-b0fNyhmzSjw,2423
|
|
59
59
|
opentf/scripts/startup.py,sha256=K-uW-70EJb4Ou2dBFR_7utDU3oMWBczkomtikq_2qCc,23119
|
|
60
60
|
opentf/toolkit/__init__.py,sha256=YnH66dmePAIU7dq_xWFYTIEUrsL9qV9f82LRDiBzbzs,22057
|
|
61
|
-
opentf/toolkit/channels.py,sha256=
|
|
61
|
+
opentf/toolkit/channels.py,sha256=7uHpQUCWCzSxcQifeUL9SB9fvsq6_9cZt_8IdBgw8FQ,26272
|
|
62
62
|
opentf/toolkit/core.py,sha256=jMBDIYZ8Qn3BvsysfKoG0iTtjOnZsggetpH3eXygCsI,9636
|
|
63
|
-
|
|
64
|
-
opentf_toolkit_nightly-0.63.0.
|
|
65
|
-
opentf_toolkit_nightly-0.63.0.
|
|
66
|
-
opentf_toolkit_nightly-0.63.0.
|
|
67
|
-
opentf_toolkit_nightly-0.63.0.
|
|
63
|
+
opentf/toolkit/models.py,sha256=PNfXVQbeyOwDfaNrLjcfhYm6duMSlNWBtZsWZcs53ag,6583
|
|
64
|
+
opentf_toolkit_nightly-0.63.0.dev1397.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
65
|
+
opentf_toolkit_nightly-0.63.0.dev1397.dist-info/METADATA,sha256=vEYD3Fg0XWwNxtheNasZuvo8NDBsGF3ffn_o52w6Ffk,2215
|
|
66
|
+
opentf_toolkit_nightly-0.63.0.dev1397.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
|
67
|
+
opentf_toolkit_nightly-0.63.0.dev1397.dist-info/top_level.txt,sha256=_gPuE6GTT6UNXy1DjtmQSfCcZb_qYA2vWmjg7a30AGk,7
|
|
68
|
+
opentf_toolkit_nightly-0.63.0.dev1397.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|