opentf-toolkit-nightly 0.50.0.dev696__py3-none-any.whl → 0.50.0.dev705__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/commons/__init__.py +58 -79
- opentf/commons/config.py +86 -17
- opentf/schemas/opentestfactory.org/v1alpha1/Subscription.json +61 -26
- opentf/toolkit/__init__.py +8 -8
- {opentf_toolkit_nightly-0.50.0.dev696.dist-info → opentf_toolkit_nightly-0.50.0.dev705.dist-info}/METADATA +1 -1
- {opentf_toolkit_nightly-0.50.0.dev696.dist-info → opentf_toolkit_nightly-0.50.0.dev705.dist-info}/RECORD +9 -9
- {opentf_toolkit_nightly-0.50.0.dev696.dist-info → opentf_toolkit_nightly-0.50.0.dev705.dist-info}/LICENSE +0 -0
- {opentf_toolkit_nightly-0.50.0.dev696.dist-info → opentf_toolkit_nightly-0.50.0.dev705.dist-info}/WHEEL +0 -0
- {opentf_toolkit_nightly-0.50.0.dev696.dist-info → opentf_toolkit_nightly-0.50.0.dev705.dist-info}/top_level.txt +0 -0
opentf/commons/__init__.py
CHANGED
|
@@ -34,8 +34,9 @@ from .config import (
|
|
|
34
34
|
ConfigError,
|
|
35
35
|
make_argparser,
|
|
36
36
|
configure_logging,
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
read_config,
|
|
38
|
+
read_descriptor,
|
|
39
|
+
get_named,
|
|
39
40
|
)
|
|
40
41
|
from .auth import (
|
|
41
42
|
initialize_authn_authz,
|
|
@@ -49,7 +50,6 @@ from .schemas import *
|
|
|
49
50
|
########################################################################
|
|
50
51
|
# Constants
|
|
51
52
|
|
|
52
|
-
|
|
53
53
|
DEFAULT_NAMESPACE = 'default'
|
|
54
54
|
|
|
55
55
|
# Misc. constants
|
|
@@ -63,13 +63,6 @@ DEFAULT_HEADERS = {
|
|
|
63
63
|
'Content-Security-Policy': 'default-src \'none\'',
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
DEFAULT_CONTEXT = {
|
|
67
|
-
'host': '127.0.0.1',
|
|
68
|
-
'port': 443,
|
|
69
|
-
'ssl_context': 'adhoc',
|
|
70
|
-
'eventbus': {'endpoint': 'https://127.0.0.1:38368', 'token': 'invalid-token'},
|
|
71
|
-
}
|
|
72
|
-
|
|
73
66
|
REASON_STATUS = {
|
|
74
67
|
'OK': 200,
|
|
75
68
|
'Created': 201,
|
|
@@ -105,6 +98,8 @@ ACCESSLOG_FORMAT = (
|
|
|
105
98
|
|
|
106
99
|
DEBUG_LEVELS = {'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'}
|
|
107
100
|
|
|
101
|
+
PARAMETERS_KEY = '__PARAMETERS__'
|
|
102
|
+
|
|
108
103
|
########################################################################
|
|
109
104
|
# Config Helpers
|
|
110
105
|
|
|
@@ -204,6 +199,39 @@ def _get_debug_level(name: str) -> str:
|
|
|
204
199
|
return level if level in DEBUG_LEVELS else 'INFO'
|
|
205
200
|
|
|
206
201
|
|
|
202
|
+
def _get_contextparameter_spec(app: Flask, name: str) -> Optional[Dict[str, Any]]:
|
|
203
|
+
"""Get context parameter specification.
|
|
204
|
+
|
|
205
|
+
Initialize cache if needed, ignoring context parameters specs from
|
|
206
|
+
other services.
|
|
207
|
+
|
|
208
|
+
Adds the `watchdog_polling_delay_seconds` spec.
|
|
209
|
+
"""
|
|
210
|
+
if PARAMETERS_KEY not in app.config:
|
|
211
|
+
app.config[PARAMETERS_KEY] = []
|
|
212
|
+
for manifest in app.config['DESCRIPTOR']:
|
|
213
|
+
if manifest.get('metadata', {}).get('name', '').lower() != app.name.lower():
|
|
214
|
+
continue
|
|
215
|
+
app.config[PARAMETERS_KEY] += manifest.get('spec', {}).get(
|
|
216
|
+
'contextParameters', []
|
|
217
|
+
)
|
|
218
|
+
app.config[PARAMETERS_KEY] += [
|
|
219
|
+
{
|
|
220
|
+
'name': 'watchdog_polling_delay_seconds',
|
|
221
|
+
'descriptiveName': 'polling delay in seconds',
|
|
222
|
+
'default': 30,
|
|
223
|
+
'type': 'int',
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
app.logger.info('Configuration:')
|
|
227
|
+
parameters = app.config[PARAMETERS_KEY]
|
|
228
|
+
try:
|
|
229
|
+
spec = get_named(name, parameters)
|
|
230
|
+
except ValueError:
|
|
231
|
+
spec = None
|
|
232
|
+
return spec
|
|
233
|
+
|
|
234
|
+
|
|
207
235
|
########################################################################
|
|
208
236
|
|
|
209
237
|
|
|
@@ -379,7 +407,7 @@ def make_app(
|
|
|
379
407
|
configfile: str,
|
|
380
408
|
schema: Optional[str] = None,
|
|
381
409
|
defaultcontext: Optional[Dict[str, Any]] = None,
|
|
382
|
-
|
|
410
|
+
descriptor: Optional[Union[str, Dict[str, Any], List[Dict[str, Any]]]] = None,
|
|
383
411
|
) -> Flask:
|
|
384
412
|
"""Create a new app.
|
|
385
413
|
|
|
@@ -393,7 +421,7 @@ def make_app(
|
|
|
393
421
|
|
|
394
422
|
- schema: a string or None (None by default)
|
|
395
423
|
- defaultcontext: a dictionary or None (None by default)
|
|
396
|
-
-
|
|
424
|
+
- descriptor: a filename, a dictionary or a list of dictionaries or
|
|
397
425
|
None (None by default)
|
|
398
426
|
|
|
399
427
|
# Returned value
|
|
@@ -403,7 +431,7 @@ def make_app(
|
|
|
403
431
|
|
|
404
432
|
`CONFIG` is a dictionary, the complete config file. `CONTEXT` is a
|
|
405
433
|
subset of `CONFIG`, the current entry in `CONFIG['context']`. It is
|
|
406
|
-
also a dictionary. `
|
|
434
|
+
also a dictionary. `DESCRIPTOR` is the service descriptor.
|
|
407
435
|
|
|
408
436
|
# Raised Exception
|
|
409
437
|
|
|
@@ -415,27 +443,13 @@ def make_app(
|
|
|
415
443
|
configure_logging(name, _get_debug_level(name))
|
|
416
444
|
app = Flask(name)
|
|
417
445
|
try:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
'Cannot specify a context when using default configuration.'
|
|
422
|
-
)
|
|
423
|
-
context = defaultcontext or DEFAULT_CONTEXT
|
|
424
|
-
config = {}
|
|
425
|
-
else:
|
|
426
|
-
configfile, config = read_configfile(args.config, configfile)
|
|
427
|
-
valid, extra = validate_schema(schema or SERVICECONFIG, config)
|
|
428
|
-
if not valid:
|
|
429
|
-
raise ConfigError(f'Config file "{configfile}" is invalid: {extra}.')
|
|
446
|
+
context, config = read_config(
|
|
447
|
+
args.config, args.context, configfile, defaultcontext, schema
|
|
448
|
+
)
|
|
430
449
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
context = get_named(context_name, config['contexts'])['context']
|
|
434
|
-
except ValueError as err:
|
|
435
|
-
raise ConfigError(f'Could not find context "{context_name}": {err}.')
|
|
450
|
+
if args.descriptor or descriptor is None or isinstance(descriptor, str):
|
|
451
|
+
_, descriptor = read_descriptor(args.descriptor, descriptor)
|
|
436
452
|
|
|
437
|
-
if args.manifest is not None or manifest is None or isinstance(manifest, str):
|
|
438
|
-
_, manifest = read_manifest(args.manifest, manifest)
|
|
439
453
|
if args.host:
|
|
440
454
|
context['host'] = args.host
|
|
441
455
|
if args.port:
|
|
@@ -450,21 +464,14 @@ def make_app(
|
|
|
450
464
|
|
|
451
465
|
app.config['CONTEXT'] = context
|
|
452
466
|
app.config['CONFIG'] = config
|
|
453
|
-
app.config['
|
|
467
|
+
app.config['DESCRIPTOR'] = (
|
|
468
|
+
descriptor if isinstance(descriptor, list) else [descriptor]
|
|
469
|
+
)
|
|
454
470
|
app.before_request(_make_authenticator(context))
|
|
455
471
|
app.after_request(_add_securityheaders)
|
|
456
472
|
return app
|
|
457
473
|
|
|
458
474
|
|
|
459
|
-
def get_named(name: str, entries: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
460
|
-
items = [entry for entry in entries if entry.get('name') == name]
|
|
461
|
-
if not items:
|
|
462
|
-
raise ValueError(f'Found no entry with name "{name}"')
|
|
463
|
-
if len(items) > 1:
|
|
464
|
-
raise ValueError(f'Found more than one entry with name "{name}"')
|
|
465
|
-
return items.pop()
|
|
466
|
-
|
|
467
|
-
|
|
468
475
|
def get_context_parameter(
|
|
469
476
|
app: Flask, name: str, validator: Optional[Any] = None
|
|
470
477
|
) -> int:
|
|
@@ -472,10 +479,6 @@ def get_context_parameter(
|
|
|
472
479
|
|
|
473
480
|
Exits with an error code of 2 if the parameter is not an integer.
|
|
474
481
|
|
|
475
|
-
Configuration: (on first call only)
|
|
476
|
-
descriptiveName (name): %d
|
|
477
|
-
[DescriptiveName must be greater that %d, aborting.]
|
|
478
|
-
descriptiveName (name): %d (was defined as %d, but {reason})
|
|
479
482
|
# Required parameters
|
|
480
483
|
|
|
481
484
|
- app: a Flask object
|
|
@@ -503,35 +506,13 @@ def get_context_parameter(
|
|
|
503
506
|
app.logger.error(*msg)
|
|
504
507
|
sys.exit(2)
|
|
505
508
|
|
|
506
|
-
|
|
507
|
-
if '__PARAMETERS__' not in app.config:
|
|
508
|
-
app.config['__PARAMETERS__'] = []
|
|
509
|
-
for manifest in app.config['MANIFEST']:
|
|
510
|
-
if manifest.get('metadata', {}).get('name', '').lower() != app.name.lower():
|
|
511
|
-
continue
|
|
512
|
-
app.config['__PARAMETERS__'] += manifest.get('spec', {}).get(
|
|
513
|
-
'contextParameters', []
|
|
514
|
-
)
|
|
515
|
-
app.config['__PARAMETERS__'] += [
|
|
516
|
-
{
|
|
517
|
-
'name': 'watchdog_polling_delay_seconds',
|
|
518
|
-
'descriptiveName': 'polling delay in seconds',
|
|
519
|
-
'default': 30,
|
|
520
|
-
'type': 'int',
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
app.logger.info('Configuration:')
|
|
524
|
-
parameters = app.config['__PARAMETERS__']
|
|
525
|
-
try:
|
|
526
|
-
spec = get_named(name, parameters)
|
|
527
|
-
except ValueError:
|
|
528
|
-
spec = None
|
|
509
|
+
spec = _get_contextparameter_spec(app, name)
|
|
529
510
|
try:
|
|
530
511
|
if name not in app.config['CONTEXT']:
|
|
531
512
|
if spec and 'default' in spec:
|
|
532
513
|
return _maybe_validate(spec['default'])
|
|
533
514
|
_fatal(
|
|
534
|
-
'Context parameter %s not
|
|
515
|
+
'Context parameter %s not in current context and no default value specified.',
|
|
535
516
|
name,
|
|
536
517
|
)
|
|
537
518
|
|
|
@@ -540,17 +521,14 @@ def get_context_parameter(
|
|
|
540
521
|
return _maybe_validate(val)
|
|
541
522
|
if spec.get('type') == 'int':
|
|
542
523
|
val = int(val)
|
|
524
|
+
desc = spec['descriptiveName']
|
|
543
525
|
if (low := spec.get('minValue')) and val < low:
|
|
544
526
|
_fatal(
|
|
545
|
-
|
|
546
|
-
+ spec['descriptiveName'][1:]
|
|
547
|
-
+ f' must be greater than {low-1}, aborting.'
|
|
527
|
+
desc[0].upper() + desc[1:] + f' must be greater than {low-1}, aborting.'
|
|
548
528
|
)
|
|
549
529
|
if (high := spec.get('maxValue')) and val > high:
|
|
550
530
|
_fatal(
|
|
551
|
-
|
|
552
|
-
+ spec['descriptiveName'][1:]
|
|
553
|
-
+ f' must be less that {high+1}, aborting.'
|
|
531
|
+
desc[0].upper() + desc[1:] + f' must be less that {high+1}, aborting.'
|
|
554
532
|
)
|
|
555
533
|
return _maybe_validate(val)
|
|
556
534
|
except ValueError as err:
|
|
@@ -580,7 +558,7 @@ def get_context_service(app: Flask, service: str) -> Dict[str, Any]:
|
|
|
580
558
|
sys.exit(2)
|
|
581
559
|
|
|
582
560
|
|
|
583
|
-
def run_app(app) -> None:
|
|
561
|
+
def run_app(app: Flask) -> None:
|
|
584
562
|
"""Start the app.
|
|
585
563
|
|
|
586
564
|
Using waitress as the wsgi server. The logging service is
|
|
@@ -595,12 +573,13 @@ def run_app(app) -> None:
|
|
|
595
573
|
if _get_debug_level(app.name) == 'DEBUG':
|
|
596
574
|
from paste.translogger import TransLogger
|
|
597
575
|
|
|
598
|
-
|
|
576
|
+
_app = TransLogger(app, format=ACCESSLOG_FORMAT, setup_console_handler=False)
|
|
599
577
|
else:
|
|
600
578
|
logging.getLogger('waitress').setLevel('ERROR')
|
|
601
579
|
app.logger.info(f'Serving on http://{context["host"]}:{context["port"]}')
|
|
580
|
+
_app = app
|
|
602
581
|
|
|
603
|
-
serve(
|
|
582
|
+
serve(_app, host=context['host'], port=context['port'])
|
|
604
583
|
|
|
605
584
|
|
|
606
585
|
########################################################################
|
opentf/commons/config.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
"""Helpers for the OpenTestFactory config."""
|
|
16
16
|
|
|
17
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
17
18
|
|
|
18
19
|
import argparse
|
|
19
20
|
import inspect
|
|
@@ -23,11 +24,20 @@ from logging.config import dictConfig
|
|
|
23
24
|
|
|
24
25
|
import yaml
|
|
25
26
|
|
|
27
|
+
from .schemas import validate_schema, SERVICECONFIG
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
########################################################################
|
|
28
31
|
|
|
29
32
|
NOTIFICATION_LOGGER_EXCLUSIONS = 'eventbus'
|
|
30
33
|
|
|
34
|
+
DEFAULT_CONTEXT = {
|
|
35
|
+
'host': '127.0.0.1',
|
|
36
|
+
'port': 443,
|
|
37
|
+
'ssl_context': 'adhoc',
|
|
38
|
+
'eventbus': {'endpoint': 'https://127.0.0.1:38368', 'token': 'invalid-token'},
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
|
|
32
42
|
########################################################################
|
|
33
43
|
|
|
@@ -38,7 +48,7 @@ class ConfigError(Exception):
|
|
|
38
48
|
|
|
39
49
|
def make_argparser(description: str, configfile: str) -> argparse.ArgumentParser:
|
|
40
50
|
parser = argparse.ArgumentParser(description=description)
|
|
41
|
-
parser.add_argument('--
|
|
51
|
+
parser.add_argument('--descriptor', help='alternate descriptor file')
|
|
42
52
|
parser.add_argument(
|
|
43
53
|
'--config', help=f'alternate config file (default to {configfile})'
|
|
44
54
|
)
|
|
@@ -117,32 +127,91 @@ def configure_logging(name: str, debug_level: str) -> None:
|
|
|
117
127
|
dictConfig(logging_conf)
|
|
118
128
|
|
|
119
129
|
|
|
120
|
-
def
|
|
130
|
+
def _read_configfile(
|
|
131
|
+
argsconfig: Optional[str], configfile: str
|
|
132
|
+
) -> Tuple[str, Dict[str, Any]]:
|
|
121
133
|
try:
|
|
122
|
-
|
|
123
|
-
with open(
|
|
134
|
+
filename = argsconfig or configfile
|
|
135
|
+
with open(filename, 'r', encoding='utf-8') as cnf:
|
|
124
136
|
config = yaml.safe_load(cnf)
|
|
125
|
-
|
|
137
|
+
if not isinstance(config, dict):
|
|
138
|
+
raise ValueError('Config file is not an object.')
|
|
139
|
+
return filename, config
|
|
126
140
|
except Exception as err:
|
|
127
|
-
raise ConfigError(f'Could not get configfile "{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def
|
|
141
|
+
raise ConfigError(f'Could not get configfile "{filename}", aborting: {err}.')
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def read_config(
|
|
145
|
+
argsconfig: Optional[str],
|
|
146
|
+
argscontext: Optional[str],
|
|
147
|
+
configfile: str,
|
|
148
|
+
defaultcontext,
|
|
149
|
+
schema,
|
|
150
|
+
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
151
|
+
if argsconfig is None and not os.path.isfile(configfile):
|
|
152
|
+
if argscontext:
|
|
153
|
+
raise ConfigError(
|
|
154
|
+
'Cannot specify a context when using default configuration.'
|
|
155
|
+
)
|
|
156
|
+
context = defaultcontext or DEFAULT_CONTEXT
|
|
157
|
+
config = {}
|
|
158
|
+
else:
|
|
159
|
+
configfile, config = _read_configfile(argsconfig, configfile)
|
|
160
|
+
valid, extra = validate_schema(schema or SERVICECONFIG, config)
|
|
161
|
+
if not valid:
|
|
162
|
+
raise ConfigError(f'Config file "{configfile}" is invalid: {extra}.')
|
|
163
|
+
|
|
164
|
+
context_name = argscontext or config['current-context']
|
|
165
|
+
try:
|
|
166
|
+
context = get_named(context_name, config['contexts'])['context']
|
|
167
|
+
except ValueError as err:
|
|
168
|
+
raise ConfigError(f'Could not find context "{context_name}": {err}.')
|
|
169
|
+
return context, config
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def read_descriptor(
|
|
173
|
+
argsdescriptor: Optional[str], descriptor: Any
|
|
174
|
+
) -> Tuple[str, List[Dict[str, Any]]]:
|
|
131
175
|
try:
|
|
132
|
-
if
|
|
133
|
-
|
|
176
|
+
if argsdescriptor:
|
|
177
|
+
filename = argsdescriptor
|
|
134
178
|
else:
|
|
135
179
|
for frame in inspect.stack():
|
|
136
180
|
if frame.frame.f_code.co_name == '<module>':
|
|
137
181
|
break
|
|
138
182
|
else:
|
|
139
183
|
raise ConfigError('Could not get module location, aborting.')
|
|
140
|
-
|
|
184
|
+
filename = os.path.join(
|
|
141
185
|
os.path.dirname(frame.filename),
|
|
142
|
-
|
|
186
|
+
descriptor or 'service.yaml',
|
|
143
187
|
)
|
|
144
|
-
with open(
|
|
145
|
-
|
|
146
|
-
return
|
|
188
|
+
with open(filename, 'r', encoding='utf-8') as definition:
|
|
189
|
+
manifests = list(yaml.safe_load_all(definition))
|
|
190
|
+
return filename, manifests
|
|
147
191
|
except Exception as err:
|
|
148
|
-
raise ConfigError(f'Could not get
|
|
192
|
+
raise ConfigError(f'Could not get descriptor "{filename}", aborting: {err}.')
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_named(name: str, entries: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
196
|
+
"""Get an entry from a list of entries.
|
|
197
|
+
|
|
198
|
+
# Required parameters
|
|
199
|
+
|
|
200
|
+
- name: a string, the entry 'name'
|
|
201
|
+
- entries: a list of dictionaries
|
|
202
|
+
|
|
203
|
+
# Returned value
|
|
204
|
+
|
|
205
|
+
A dictionary, the entry with the 'name' `name`.
|
|
206
|
+
|
|
207
|
+
# Raised exceptions
|
|
208
|
+
|
|
209
|
+
A _ValueError_ exception is raised if no entry is found or if more
|
|
210
|
+
than one entry is found.
|
|
211
|
+
"""
|
|
212
|
+
items = [entry for entry in entries if entry.get('name') == name]
|
|
213
|
+
if not items:
|
|
214
|
+
raise ValueError(f'Found no entry with name "{name}"')
|
|
215
|
+
if len(items) > 1:
|
|
216
|
+
raise ValueError(f'Found more than one entry with name "{name}"')
|
|
217
|
+
return items.pop()
|
|
@@ -41,31 +41,7 @@
|
|
|
41
41
|
"additionalProperties": false
|
|
42
42
|
},
|
|
43
43
|
"matchExpressions": {
|
|
44
|
-
"
|
|
45
|
-
"minItems": 1,
|
|
46
|
-
"items":{
|
|
47
|
-
"type": "object",
|
|
48
|
-
"properties": {
|
|
49
|
-
"key": { "type": "string" },
|
|
50
|
-
"operator": {
|
|
51
|
-
"enum": [
|
|
52
|
-
"In",
|
|
53
|
-
"NotIn",
|
|
54
|
-
"Exists",
|
|
55
|
-
"DoesNotExist"
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
"values": {
|
|
59
|
-
"type": "array",
|
|
60
|
-
"minItems": 0,
|
|
61
|
-
"items": {
|
|
62
|
-
"type": "string"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
"additionalProperties": false,
|
|
67
|
-
"required": [ "key", "operator" ]
|
|
68
|
-
}
|
|
44
|
+
"$ref": "#/definitions/expressions"
|
|
69
45
|
},
|
|
70
46
|
"matchFields": {
|
|
71
47
|
"type": "object",
|
|
@@ -76,6 +52,9 @@
|
|
|
76
52
|
},
|
|
77
53
|
"minProperties": 1,
|
|
78
54
|
"additionalProperties": false
|
|
55
|
+
},
|
|
56
|
+
"matchFieldExpressions": {
|
|
57
|
+
"$ref": "#/definitions/expressions"
|
|
79
58
|
}
|
|
80
59
|
},
|
|
81
60
|
"minProperties": 1,
|
|
@@ -109,5 +88,61 @@
|
|
|
109
88
|
"metadata",
|
|
110
89
|
"spec"
|
|
111
90
|
],
|
|
112
|
-
"additionalProperties": false
|
|
91
|
+
"additionalProperties": false,
|
|
92
|
+
"definitions": {
|
|
93
|
+
"expressions": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"minItems": 1,
|
|
96
|
+
"items": {
|
|
97
|
+
"anyOf": [
|
|
98
|
+
{
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {
|
|
101
|
+
"key": {
|
|
102
|
+
"type": "string"
|
|
103
|
+
},
|
|
104
|
+
"operator": {
|
|
105
|
+
"enum": [
|
|
106
|
+
"In",
|
|
107
|
+
"NotIn"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"values": {
|
|
111
|
+
"type": "array",
|
|
112
|
+
"minItems": 1,
|
|
113
|
+
"items": {
|
|
114
|
+
"type": "string"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"additionalProperties": false,
|
|
119
|
+
"required": [
|
|
120
|
+
"key",
|
|
121
|
+
"operator",
|
|
122
|
+
"values"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"key": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"operator": {
|
|
132
|
+
"enum": [
|
|
133
|
+
"Exists",
|
|
134
|
+
"DoesNotExist"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"additionalProperties": false,
|
|
139
|
+
"required": [
|
|
140
|
+
"key",
|
|
141
|
+
"operator"
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
113
148
|
}
|
opentf/toolkit/__init__.py
CHANGED
|
@@ -409,7 +409,7 @@ def run_plugin(plugin):
|
|
|
409
409
|
context[SUBSCRIPTION_KEY] = []
|
|
410
410
|
context[INPUTS_KEY] = {}
|
|
411
411
|
if context[KIND_KEY] == PROVIDERCOMMAND:
|
|
412
|
-
for manifest in plugin.config['
|
|
412
|
+
for manifest in plugin.config['DESCRIPTOR']:
|
|
413
413
|
metadata = manifest.get('metadata', {})
|
|
414
414
|
if metadata.get('name', '').lower() != plugin.name.lower():
|
|
415
415
|
continue
|
|
@@ -445,7 +445,7 @@ def make_plugin(
|
|
|
445
445
|
provider: Optional[Handler] = None,
|
|
446
446
|
providers: Optional[Dict[str, Handler]] = None,
|
|
447
447
|
publisher: Optional[Handler] = None,
|
|
448
|
-
|
|
448
|
+
descriptor=None,
|
|
449
449
|
schema=None,
|
|
450
450
|
configfile=None,
|
|
451
451
|
):
|
|
@@ -454,7 +454,7 @@ def make_plugin(
|
|
|
454
454
|
One and only one of `channel`, `generator`, `provider`, `providers`,
|
|
455
455
|
or `publisher` must be specified.
|
|
456
456
|
|
|
457
|
-
If no `
|
|
457
|
+
If no `descriptor` is specified, there must be `plugin.yaml` file in
|
|
458
458
|
the same directory as the caller source file. If none is found the
|
|
459
459
|
execution stops.
|
|
460
460
|
|
|
@@ -472,8 +472,8 @@ def make_plugin(
|
|
|
472
472
|
|
|
473
473
|
# Optional parameters
|
|
474
474
|
|
|
475
|
-
-
|
|
476
|
-
default)
|
|
475
|
+
- descriptor: a dictionary or a list of dictionaries or None (None
|
|
476
|
+
by default)
|
|
477
477
|
- schema: a string or None (None by default)
|
|
478
478
|
- configfile: a string or None (None by default)
|
|
479
479
|
|
|
@@ -522,9 +522,9 @@ def make_plugin(
|
|
|
522
522
|
raise ValueError(
|
|
523
523
|
"One and only one of 'channel', 'generator', 'provider', 'providers', or 'publisher' is required."
|
|
524
524
|
)
|
|
525
|
-
if not (
|
|
525
|
+
if not (descriptor is None or isinstance(descriptor, (dict, list))):
|
|
526
526
|
raise ValueError(
|
|
527
|
-
"'
|
|
527
|
+
"'descriptor', if specified, must be a dictionary or a list of dictionaries."
|
|
528
528
|
)
|
|
529
529
|
|
|
530
530
|
kind = (
|
|
@@ -542,7 +542,7 @@ def make_plugin(
|
|
|
542
542
|
description,
|
|
543
543
|
configfile=configfile or f'conf/{name}.yaml',
|
|
544
544
|
schema=schema,
|
|
545
|
-
|
|
545
|
+
descriptor=descriptor if descriptor is not None else 'plugin.yaml',
|
|
546
546
|
)
|
|
547
547
|
plugin.route('/inbox', methods=['POST'])(process_inbox)
|
|
548
548
|
plugin.config['CONTEXT'][KIND_KEY] = kind
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
opentf/commons/__init__.py,sha256=
|
|
1
|
+
opentf/commons/__init__.py,sha256=06GO6FXo0zq7IJzDr4718FQ5IIze6EyReC0UACF3L0M,22063
|
|
2
2
|
opentf/commons/auth.py,sha256=ADMygTSGy6voOQUAD9EtusPR_hgJ6jUK_uJE8CCpBoE,14481
|
|
3
|
-
opentf/commons/config.py,sha256=
|
|
3
|
+
opentf/commons/config.py,sha256=CJ3xGc9dmva08zek9FIMqFHkgwoPOukiXmafN2nm000,7006
|
|
4
4
|
opentf/commons/expressions.py,sha256=dKcYYc7j3laueZEcV00djvE7UFPLMiUTNeNvKGla3cQ,19069
|
|
5
5
|
opentf/commons/pubsub.py,sha256=_Psa3wdE_OB6wDHhUwr9zdxHpu5rSz19qtpM1Qo2WsY,5675
|
|
6
6
|
opentf/commons/schemas.py,sha256=kMhZH47dpBk_HDMZk-1AlTb1dIYF-N6SPiEUmkEtWX0,3909
|
|
@@ -25,7 +25,7 @@ opentf/schemas/opentestfactory.org/v1alpha1/QualityGate.json,sha256=BLIPkLiENjN-
|
|
|
25
25
|
opentf/schemas/opentestfactory.org/v1alpha1/RetentionPolicy.json,sha256=EvTha15eFVbvaZhzy60IeHt-oKOqOZVWiPuS3PZejTI,1182
|
|
26
26
|
opentf/schemas/opentestfactory.org/v1alpha1/SSHServiceConfig.json,sha256=qqFoI-Ltn6O25YgjSiumhh0KEgm-Ftl3v56AxnCP7Cs,6301
|
|
27
27
|
opentf/schemas/opentestfactory.org/v1alpha1/ServiceConfig.json,sha256=hRhJj4CkvB6p0IKKLMIwyxnbTmJkyEiDJSUxqzv0exI,2835
|
|
28
|
-
opentf/schemas/opentestfactory.org/v1alpha1/Subscription.json,sha256=
|
|
28
|
+
opentf/schemas/opentestfactory.org/v1alpha1/Subscription.json,sha256=sc32NWjbmw2fFlvU6Guh7VhlHfp7kLzOisDFL6-BDRU,5031
|
|
29
29
|
opentf/schemas/opentestfactory.org/v1alpha1/Workflow.json,sha256=PKuAGeyQnGjoYNbd3IUng4k8SIecIVelXzzOncewhyw,9899
|
|
30
30
|
opentf/schemas/opentestfactory.org/v1alpha1/WorkflowCanceled.json,sha256=hLGQnrSXjvKZTzegucHrtGjAgS8dZeC7dZZ6mZVvclc,1351
|
|
31
31
|
opentf/schemas/opentestfactory.org/v1alpha1/WorkflowCompleted.json,sha256=O1wpwhSawF9u64RtX38Lv2sptq8w3q8dvdvHxFwG20U,1292
|
|
@@ -42,11 +42,11 @@ opentf/schemas/opentestfactory.org/v1beta1/Workflow.json,sha256=QZ8mM9PhzsI9gTmw
|
|
|
42
42
|
opentf/schemas/opentestfactory.org/v1beta2/ServiceConfig.json,sha256=rEvK2YWL5lG94_qYgR_GnLWNsaQhaQ-2kuZdWJr5NnY,3517
|
|
43
43
|
opentf/scripts/launch_java_service.sh,sha256=Ut_STdFqMhTV7cekPVp4JkBTqZKcJSAh1UNUn9Ylv3g,2427
|
|
44
44
|
opentf/scripts/startup.py,sha256=CjKrFqbLyDKdUca-fWB-QkDURjdypkbrBUpEFNy3qQo,18719
|
|
45
|
-
opentf/toolkit/__init__.py,sha256=
|
|
45
|
+
opentf/toolkit/__init__.py,sha256=4bL02Q3DgtMWZhaCvlJ7eOp9rPGIG9p-xNcs8hynhhQ,18371
|
|
46
46
|
opentf/toolkit/channels.py,sha256=GGIPuUN7sFM_FwoqbUgLk7smggLty3rSDZTma5jAAt4,16185
|
|
47
47
|
opentf/toolkit/core.py,sha256=40S-pUKXWidbI4JwkmQjGq46WlxJshj6d-wEl4e5TxU,7312
|
|
48
|
-
opentf_toolkit_nightly-0.50.0.
|
|
49
|
-
opentf_toolkit_nightly-0.50.0.
|
|
50
|
-
opentf_toolkit_nightly-0.50.0.
|
|
51
|
-
opentf_toolkit_nightly-0.50.0.
|
|
52
|
-
opentf_toolkit_nightly-0.50.0.
|
|
48
|
+
opentf_toolkit_nightly-0.50.0.dev705.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
49
|
+
opentf_toolkit_nightly-0.50.0.dev705.dist-info/METADATA,sha256=2BuuxKrik9f7f_7FliEYZwaqUaov-XJbjPyIzAe4Er4,1965
|
|
50
|
+
opentf_toolkit_nightly-0.50.0.dev705.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
|
|
51
|
+
opentf_toolkit_nightly-0.50.0.dev705.dist-info/top_level.txt,sha256=_gPuE6GTT6UNXy1DjtmQSfCcZb_qYA2vWmjg7a30AGk,7
|
|
52
|
+
opentf_toolkit_nightly-0.50.0.dev705.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|