qontract-reconcile 0.10.2.dev485__py3-none-any.whl → 0.10.2.dev494__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.
- {qontract_reconcile-0.10.2.dev485.dist-info → qontract_reconcile-0.10.2.dev494.dist-info}/METADATA +4 -2
- {qontract_reconcile-0.10.2.dev485.dist-info → qontract_reconcile-0.10.2.dev494.dist-info}/RECORD +15 -7
- reconcile/cli.py +25 -1
- reconcile/gql_definitions/common/vcs.py +91 -0
- reconcile/gql_definitions/slack_usergroups_api/__init__.py +0 -0
- reconcile/gql_definitions/slack_usergroups_api/clusters.py +76 -0
- reconcile/gql_definitions/slack_usergroups_api/permissions.py +173 -0
- reconcile/gql_definitions/slack_usergroups_api/roles.py +135 -0
- reconcile/gql_definitions/slack_usergroups_api/users.py +111 -0
- reconcile/slack_usergroups_api.py +672 -0
- reconcile/typed_queries/vcs.py +42 -0
- reconcile/utils/runtime/integration.py +99 -0
- reconcile/utils/runtime/runner.py +28 -7
- {qontract_reconcile-0.10.2.dev485.dist-info → qontract_reconcile-0.10.2.dev494.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev485.dist-info → qontract_reconcile-0.10.2.dev494.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from qontract_utils.vcs import Provider
|
|
5
|
+
|
|
6
|
+
from reconcile.gql_definitions.common.vcs import query
|
|
7
|
+
from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
|
|
8
|
+
from reconcile.utils import gql
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Vcs(BaseModel):
|
|
12
|
+
name: str
|
|
13
|
+
url: str
|
|
14
|
+
default: bool = False
|
|
15
|
+
token: VaultSecret
|
|
16
|
+
provider: Provider
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_vcs_instances(query_func: Callable | None = None) -> list[Vcs]:
|
|
20
|
+
if not query_func:
|
|
21
|
+
query_func = gql.get_api().query
|
|
22
|
+
data = query(query_func=query_func)
|
|
23
|
+
vcs = [
|
|
24
|
+
Vcs(
|
|
25
|
+
name=gh_org.name,
|
|
26
|
+
url=gh_org.url,
|
|
27
|
+
default=gh_org.default or False,
|
|
28
|
+
token=gh_org.token,
|
|
29
|
+
provider=Provider.GITHUB,
|
|
30
|
+
)
|
|
31
|
+
for gh_org in data.gh_orgs or []
|
|
32
|
+
]
|
|
33
|
+
vcs += [
|
|
34
|
+
Vcs(
|
|
35
|
+
name=gl_instance.name,
|
|
36
|
+
url=gl_instance.url,
|
|
37
|
+
token=gl_instance.token,
|
|
38
|
+
provider=Provider.GITLAB,
|
|
39
|
+
)
|
|
40
|
+
for gl_instance in data.gl_instances or []
|
|
41
|
+
]
|
|
42
|
+
return vcs
|
|
@@ -10,12 +10,16 @@ from typing import (
|
|
|
10
10
|
Optional,
|
|
11
11
|
TypeVar,
|
|
12
12
|
)
|
|
13
|
+
from urllib.parse import urlparse
|
|
13
14
|
|
|
15
|
+
from httpx import Response
|
|
14
16
|
from pydantic import BaseModel
|
|
17
|
+
from qontract_api_client.client import AuthenticatedClient
|
|
15
18
|
|
|
16
19
|
from reconcile.typed_queries.app_interface_vault_settings import (
|
|
17
20
|
get_app_interface_vault_settings,
|
|
18
21
|
)
|
|
22
|
+
from reconcile.utils.config import get_config
|
|
19
23
|
from reconcile.utils.secret_reader import (
|
|
20
24
|
SecretReaderBase,
|
|
21
25
|
create_secret_reader,
|
|
@@ -238,7 +242,58 @@ class QontractReconcileIntegration[RunParamsTypeVar: RunParams](ABC):
|
|
|
238
242
|
)
|
|
239
243
|
|
|
240
244
|
|
|
245
|
+
class QontractReconcileApiIntegration[RunParamsTypeVar: RunParams](ABC):
|
|
246
|
+
"""
|
|
247
|
+
The base class for all integrations using the Qontract API.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def __init__(self, params: RunParamsTypeVar) -> None:
|
|
251
|
+
self.params: RunParamsTypeVar = params
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
@abstractmethod
|
|
255
|
+
def name(self) -> str: ...
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
async def _raise_on_4xx_5xx(response: Response) -> None:
|
|
259
|
+
response.raise_for_status()
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def qontract_api_client(self) -> AuthenticatedClient:
|
|
263
|
+
"""
|
|
264
|
+
Returns the qontract-api client.
|
|
265
|
+
"""
|
|
266
|
+
config = get_config()
|
|
267
|
+
return AuthenticatedClient(
|
|
268
|
+
base_url=urlparse(config["qontract-api"]["server"]).geturl(),
|
|
269
|
+
token=config["qontract-api"].get("token", ""),
|
|
270
|
+
httpx_args={
|
|
271
|
+
"event_hooks": {"response": [self._raise_on_4xx_5xx]},
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def secret_manager_url(self) -> str:
|
|
277
|
+
"""
|
|
278
|
+
Returns the configured secret manager URL.
|
|
279
|
+
"""
|
|
280
|
+
config = get_config()
|
|
281
|
+
return config["vault"]["server"]
|
|
282
|
+
|
|
283
|
+
@abstractmethod
|
|
284
|
+
async def async_run(self, dry_run: bool) -> None:
|
|
285
|
+
"""
|
|
286
|
+
The `async_run` function of a QontractReconcileIntegration is the asynchronous
|
|
287
|
+
entry point to its actual functionality. It is obliged to honor the `dry_run`
|
|
288
|
+
argument and not perform any changes to the system if it is set to `True`.
|
|
289
|
+
At the same time the integration should progress as far as possible in the dry-run
|
|
290
|
+
mode to highlight any issues that would have prevented it from running in non-dry-run
|
|
291
|
+
mode.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
|
|
241
295
|
RUN_FUNCTION = "run"
|
|
296
|
+
ASYNC_RUN_FUNCTION = "async_run"
|
|
242
297
|
NAME_FIELD = "QONTRACT_INTEGRATION"
|
|
243
298
|
EARLY_EXIT_DESIRED_STATE_FUNCTION = "early_exit_desired_state"
|
|
244
299
|
DESIRED_STATE_SHARD_CONFIG_FUNCTION = "desired_state_shard_config"
|
|
@@ -308,3 +363,47 @@ class ModuleBasedQontractReconcileIntegration(
|
|
|
308
363
|
|
|
309
364
|
def run(self, dry_run: bool) -> None:
|
|
310
365
|
self.params.module.run(dry_run, *self.params.args, **self.params.kwargs)
|
|
366
|
+
|
|
367
|
+
async def async_run(self, dry_run: bool) -> None:
|
|
368
|
+
await self.params.module.async_run(
|
|
369
|
+
dry_run, *self.params.args, **self.params.kwargs
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class ModuleBasedQontractReconcileApiIntegration(
|
|
374
|
+
QontractReconcileApiIntegration[ModuleArgsKwargsRunParams]
|
|
375
|
+
):
|
|
376
|
+
"""
|
|
377
|
+
Since most integrations are implemented as modules, this class provides a
|
|
378
|
+
wrapper around a module that implements the `QontractReconcileIntegration`
|
|
379
|
+
interface. This way such module based integrations can be used as if they
|
|
380
|
+
were instances of the `QontractReconcileIntegration` class.
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
def __init__(self, params: ModuleArgsKwargsRunParams):
|
|
384
|
+
super().__init__(params)
|
|
385
|
+
# self.name # run to check if the name can be extracted from the module
|
|
386
|
+
if not self._integration_supports(NAME_FIELD):
|
|
387
|
+
raise NotImplementedError(f"Integration has no {NAME_FIELD} field")
|
|
388
|
+
if not self._integration_supports(ASYNC_RUN_FUNCTION):
|
|
389
|
+
raise NotImplementedError(
|
|
390
|
+
f"Integration has no {ASYNC_RUN_FUNCTION}() function"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def _integration_supports(self, func_name: str) -> bool:
|
|
394
|
+
"""
|
|
395
|
+
Verifies, that an integration supports a specific function.
|
|
396
|
+
todo: more thorough verification of the functions signature would be required.
|
|
397
|
+
"""
|
|
398
|
+
return func_name in dir(self.params.module)
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def name(self) -> str:
|
|
402
|
+
if self._integration_supports(NAME_FIELD):
|
|
403
|
+
return self.params.module.QONTRACT_INTEGRATION.replace("_", "-")
|
|
404
|
+
raise NotImplementedError("Integration missing QONTRACT_INTEGRATION.")
|
|
405
|
+
|
|
406
|
+
async def async_run(self, dry_run: bool) -> None:
|
|
407
|
+
await self.params.module.async_run(
|
|
408
|
+
dry_run, *self.params.args, **self.params.kwargs
|
|
409
|
+
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
3
|
import sys
|
|
3
4
|
from dataclasses import dataclass
|
|
@@ -15,6 +16,7 @@ from reconcile.utils.runtime.desired_state_diff import (
|
|
|
15
16
|
build_desired_state_diff,
|
|
16
17
|
)
|
|
17
18
|
from reconcile.utils.runtime.integration import (
|
|
19
|
+
QontractReconcileApiIntegration,
|
|
18
20
|
QontractReconcileIntegration,
|
|
19
21
|
RunParams,
|
|
20
22
|
)
|
|
@@ -28,7 +30,7 @@ class IntegrationRunConfiguration:
|
|
|
28
30
|
Holds all required context and configuration for an integration run.
|
|
29
31
|
"""
|
|
30
32
|
|
|
31
|
-
integration: QontractReconcileIntegration
|
|
33
|
+
integration: QontractReconcileIntegration | QontractReconcileApiIntegration
|
|
32
34
|
valdiate_schemas: bool
|
|
33
35
|
"""
|
|
34
36
|
Whether to fail an integration if it queries schemas it is not allowed to.
|
|
@@ -66,10 +68,18 @@ class IntegrationRunConfiguration:
|
|
|
66
68
|
"""
|
|
67
69
|
|
|
68
70
|
def main_bundle_desired_state(self) -> dict[str, Any] | None:
|
|
71
|
+
if not isinstance(self.integration, QontractReconcileIntegration):
|
|
72
|
+
raise RuntimeError(
|
|
73
|
+
"main_bundle_desired_state is only available for QontractReconcileIntegration"
|
|
74
|
+
)
|
|
69
75
|
self.switch_to_main_bundle()
|
|
70
76
|
return self.integration.get_early_exit_desired_state()
|
|
71
77
|
|
|
72
78
|
def comparison_bundle_desired_state(self) -> dict[str, Any] | None:
|
|
79
|
+
if not isinstance(self.integration, QontractReconcileIntegration):
|
|
80
|
+
raise RuntimeError(
|
|
81
|
+
"comparison_bundle_desired_state is only available for QontractReconcileIntegration"
|
|
82
|
+
)
|
|
73
83
|
self.switch_to_comparison_bundle()
|
|
74
84
|
data = self.integration.get_early_exit_desired_state()
|
|
75
85
|
self.switch_to_main_bundle()
|
|
@@ -133,6 +143,7 @@ def get_desired_state_diff(
|
|
|
133
143
|
logging.exception("Failed to fetch desired state for current bundle")
|
|
134
144
|
return None
|
|
135
145
|
|
|
146
|
+
assert isinstance(run_cfg.integration, QontractReconcileIntegration)
|
|
136
147
|
return build_desired_state_diff(
|
|
137
148
|
run_cfg.integration.get_desired_state_shard_config()
|
|
138
149
|
if run_cfg.check_only_affected_shards
|
|
@@ -157,16 +168,21 @@ def run_integration_cfg(run_cfg: IntegrationRunConfiguration) -> None:
|
|
|
157
168
|
|
|
158
169
|
|
|
159
170
|
def _integration_wet_run[RunParamsTypeVar: RunParams](
|
|
160
|
-
integration: QontractReconcileIntegration[RunParamsTypeVar]
|
|
171
|
+
integration: QontractReconcileIntegration[RunParamsTypeVar]
|
|
172
|
+
| QontractReconcileApiIntegration[RunParamsTypeVar],
|
|
161
173
|
) -> None:
|
|
162
174
|
"""
|
|
163
175
|
Runs an integration in wet mode, i.e. not in dry-run mode.
|
|
164
176
|
"""
|
|
165
|
-
integration
|
|
177
|
+
if isinstance(integration, QontractReconcileIntegration):
|
|
178
|
+
integration.run(False)
|
|
179
|
+
else:
|
|
180
|
+
asyncio.run(integration.async_run(False))
|
|
166
181
|
|
|
167
182
|
|
|
168
183
|
def _integration_dry_run[RunParamsTypeVar: RunParams](
|
|
169
|
-
integration: QontractReconcileIntegration[RunParamsTypeVar]
|
|
184
|
+
integration: QontractReconcileIntegration[RunParamsTypeVar]
|
|
185
|
+
| QontractReconcileApiIntegration[RunParamsTypeVar],
|
|
170
186
|
desired_state_diff: DesiredStateDiff | None,
|
|
171
187
|
) -> None:
|
|
172
188
|
"""
|
|
@@ -189,7 +205,8 @@ def _integration_dry_run[RunParamsTypeVar: RunParams](
|
|
|
189
205
|
# we can still try to run the integration in sharded mode on the
|
|
190
206
|
# affected shards only
|
|
191
207
|
if (
|
|
192
|
-
integration
|
|
208
|
+
isinstance(integration, QontractReconcileIntegration)
|
|
209
|
+
and integration.supports_sharded_dry_run_mode()
|
|
193
210
|
and not integration.params_have_shard_info() # already running in sharded mode?
|
|
194
211
|
and desired_state_diff
|
|
195
212
|
and desired_state_diff.affected_shards
|
|
@@ -201,6 +218,7 @@ def _integration_dry_run[RunParamsTypeVar: RunParams](
|
|
|
201
218
|
sharded_integration = integration.build_integration_instance_for_shard(
|
|
202
219
|
shard
|
|
203
220
|
)
|
|
221
|
+
# TODO: support async mode for sharded dry-runs
|
|
204
222
|
sharded_integration.run(True)
|
|
205
223
|
|
|
206
224
|
# run all shards
|
|
@@ -220,8 +238,11 @@ def _integration_dry_run[RunParamsTypeVar: RunParams](
|
|
|
220
238
|
else:
|
|
221
239
|
return
|
|
222
240
|
|
|
223
|
-
|
|
224
|
-
|
|
241
|
+
if isinstance(integration, QontractReconcileIntegration):
|
|
242
|
+
# if not, we run the integration in full
|
|
243
|
+
integration.run(True)
|
|
244
|
+
else:
|
|
245
|
+
asyncio.run(integration.async_run(True))
|
|
225
246
|
|
|
226
247
|
|
|
227
248
|
def _is_task_result_an_error(result: Any) -> bool:
|
{qontract_reconcile-0.10.2.dev485.dist-info → qontract_reconcile-0.10.2.dev494.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|