qontract-reconcile 0.10.1rc746__py3-none-any.whl → 0.10.1rc748__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.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/RECORD +9 -9
- reconcile/cli.py +16 -2
- reconcile/jira_permissions_validator.py +70 -16
- reconcile/utils/jira_client.py +14 -7
- reconcile/utils/vcs.py +6 -1
- {qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc748
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/RECORD
RENAMED
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
|
|
10
10
|
reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
|
11
11
|
reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
|
12
12
|
reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
|
13
|
-
reconcile/cli.py,sha256=
|
13
|
+
reconcile/cli.py,sha256=Vw8P6w0nPOGkMRvxpn4lR75jUeRoXLJ4R-WspWNX6Cg,98702
|
14
14
|
reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
|
15
15
|
reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
|
16
16
|
reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
|
@@ -47,7 +47,7 @@ reconcile/jenkins_roles.py,sha256=f8ELpZY36UjoaCpR_9LijQuIMuB6a7sVLFf_H1ct9Hc,44
|
|
47
47
|
reconcile/jenkins_webhooks.py,sha256=j8vhJMWcRhOdc9XzRSm0CPj84jsF3e4Syjm7r1BIsDE,1978
|
48
48
|
reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffFnDGau9aY,1539
|
49
49
|
reconcile/jenkins_worker_fleets.py,sha256=PMNGOX0krubFjInPiFT0za0KCiWBLEcVDuXdKRd1BrE,5378
|
50
|
-
reconcile/jira_permissions_validator.py,sha256=
|
50
|
+
reconcile/jira_permissions_validator.py,sha256=iDsFdGqB8Zv9cjIVgYFq_N3xtRCrcR5uAZmcc51D-2o,13240
|
51
51
|
reconcile/jira_watcher.py,sha256=eyOQ92t8TFi6gogfNTO448h_h1CUyr24E0MPHc51R-o,3617
|
52
52
|
reconcile/ldap_users.py,sha256=uEWQ0V41tN9KCZi4ZKPamjrJ6djSpdpvDBo7yJ0e7ZI,3008
|
53
53
|
reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
|
@@ -618,7 +618,7 @@ reconcile/utils/helpers.py,sha256=k9svgFFZG7H5FvHYY0g5jJyvgvh2UDZxf0Ib221teag,11
|
|
618
618
|
reconcile/utils/imap_client.py,sha256=byFAJATbITJPsGECSbvXBOcCnoeTUpDFiEjzOAxLm_U,1975
|
619
619
|
reconcile/utils/instrumented_wrappers.py,sha256=eVwMoa6FCrYxLv3RML3WpZF9qKVfCTjMxphgVXG03OM,1073
|
620
620
|
reconcile/utils/jenkins_api.py,sha256=MyJSB_S3uYf3sXnt9t03-gZNQ7tbdd7Wusv3MoF2fRc,7113
|
621
|
-
reconcile/utils/jira_client.py,sha256=
|
621
|
+
reconcile/utils/jira_client.py,sha256=sCswWH-SctzPSElI9_oHv9LKuNnPfjP_FIOizxqh6nE,7693
|
622
622
|
reconcile/utils/jjb_client.py,sha256=Pdy0dLCFvD6GPCaC0tZydYgkVJPOxYXIiwWECZaFJBU,14551
|
623
623
|
reconcile/utils/jsonpath.py,sha256=NRpAEijKN4cMDjo7qivNPqpm0__GQQ1TiE0PBEBO45s,5572
|
624
624
|
reconcile/utils/jump_host.py,sha256=AdwmCZYNhRe53VwV2iAsUdVyUdVtSd4REmdThJDkM5w,4973
|
@@ -664,7 +664,7 @@ reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM
|
|
664
664
|
reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,3601
|
665
665
|
reconcile/utils/vault.py,sha256=S0eHqvZ9N3fya1E8YDaUffEvLk_fdtpzL4rvWn6f828,14991
|
666
666
|
reconcile/utils/vaultsecretref.py,sha256=3Ed2uBy36TzSvL0B-l4FoWQqB2SbBKDKEuUPIO608Bo,931
|
667
|
-
reconcile/utils/vcs.py,sha256=
|
667
|
+
reconcile/utils/vcs.py,sha256=iiAWQXNftKIRoakXEOPT6ubB_ybSuInIQ6jcMxa_NKk,8558
|
668
668
|
reconcile/utils/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
669
669
|
reconcile/utils/acs/base.py,sha256=kjcxLGIMe8oLNFOxZ_bDcClFqGCL7Auwug5is0yNGbw,2555
|
670
670
|
reconcile/utils/acs/notifiers.py,sha256=2n5blP9N1FdGLZuy3do9bpjd8NKg88kmLNNqhAGn8w4,5013
|
@@ -778,8 +778,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
778
778
|
tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
|
779
779
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
780
780
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
781
|
-
qontract_reconcile-0.10.
|
782
|
-
qontract_reconcile-0.10.
|
783
|
-
qontract_reconcile-0.10.
|
784
|
-
qontract_reconcile-0.10.
|
785
|
-
qontract_reconcile-0.10.
|
781
|
+
qontract_reconcile-0.10.1rc748.dist-info/METADATA,sha256=ceTfijoo-x5F6mo0WQjXgfkmBWkj6OvaecXkCj0R2-I,2382
|
782
|
+
qontract_reconcile-0.10.1rc748.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
783
|
+
qontract_reconcile-0.10.1rc748.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
|
784
|
+
qontract_reconcile-0.10.1rc748.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
785
|
+
qontract_reconcile-0.10.1rc748.dist-info/RECORD,,
|
reconcile/cli.py
CHANGED
@@ -1144,12 +1144,26 @@ def jenkins_webhooks_cleaner(ctx):
|
|
1144
1144
|
help="Throw and error in case of board permission errors. Useful for PR checks.",
|
1145
1145
|
default=True,
|
1146
1146
|
)
|
1147
|
+
@enable_extended_early_exit
|
1148
|
+
@extended_early_exit_cache_ttl_seconds
|
1149
|
+
@log_cached_log_output
|
1147
1150
|
@click.pass_context
|
1148
|
-
def jira_permissions_validator(
|
1151
|
+
def jira_permissions_validator(
|
1152
|
+
ctx,
|
1153
|
+
exit_on_permission_errors,
|
1154
|
+
enable_extended_early_exit,
|
1155
|
+
extended_early_exit_cache_ttl_seconds,
|
1156
|
+
log_cached_log_output,
|
1157
|
+
):
|
1149
1158
|
import reconcile.jira_permissions_validator
|
1150
1159
|
|
1151
1160
|
run_integration(
|
1152
|
-
reconcile.jira_permissions_validator,
|
1161
|
+
reconcile.jira_permissions_validator,
|
1162
|
+
ctx.obj,
|
1163
|
+
exit_on_permission_errors,
|
1164
|
+
enable_extended_early_exit=enable_extended_early_exit,
|
1165
|
+
extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
|
1166
|
+
log_cached_log_output=log_cached_log_output,
|
1153
1167
|
)
|
1154
1168
|
|
1155
1169
|
|
@@ -2,14 +2,11 @@ import logging
|
|
2
2
|
import sys
|
3
3
|
from collections.abc import Callable, Iterable
|
4
4
|
from enum import IntFlag, auto
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any, TypedDict
|
6
6
|
|
7
7
|
from jira import JIRAError
|
8
8
|
from pydantic import BaseModel
|
9
9
|
|
10
|
-
from reconcile.gql_definitions.jira_permissions_validator.jira_boards_for_permissions_validator import (
|
11
|
-
DEFINITION as JIRA_BOARDS_DEFINITION,
|
12
|
-
)
|
13
10
|
from reconcile.gql_definitions.jira_permissions_validator.jira_boards_for_permissions_validator import (
|
14
11
|
JiraBoardV1,
|
15
12
|
)
|
@@ -24,10 +21,17 @@ from reconcile.typed_queries.jira_settings import get_jira_settings
|
|
24
21
|
from reconcile.typed_queries.jiralert_settings import get_jiralert_settings
|
25
22
|
from reconcile.utils import gql, metrics
|
26
23
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
24
|
+
from reconcile.utils.extended_early_exit import (
|
25
|
+
ExtendedEarlyExitRunnerResult,
|
26
|
+
extended_early_exit_run,
|
27
|
+
)
|
27
28
|
from reconcile.utils.jira_client import JiraClient, JiraWatcherSettings
|
28
29
|
from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
|
30
|
+
from reconcile.utils.semver_helper import make_semver
|
31
|
+
from reconcile.utils.unleash import get_feature_toggle_state
|
29
32
|
|
30
33
|
QONTRACT_INTEGRATION = "jira-permissions-validator"
|
34
|
+
QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
|
31
35
|
|
32
36
|
NameToIdMap = dict[str, str]
|
33
37
|
|
@@ -59,6 +63,15 @@ class ValidationError(IntFlag):
|
|
59
63
|
INVALID_COMPONENT = auto()
|
60
64
|
|
61
65
|
|
66
|
+
class RunnerParams(TypedDict):
|
67
|
+
exit_on_permission_errors: bool
|
68
|
+
boards: list[JiraBoardV1]
|
69
|
+
|
70
|
+
|
71
|
+
class CacheSource(TypedDict):
|
72
|
+
boards: list
|
73
|
+
|
74
|
+
|
62
75
|
def board_is_valid(
|
63
76
|
jira: JiraClient,
|
64
77
|
board: JiraBoardV1,
|
@@ -90,7 +103,7 @@ def board_is_valid(
|
|
90
103
|
error |= ValidationError.INVALID_COMPONENT
|
91
104
|
|
92
105
|
issue_type = board.issue_type if board.issue_type else default_issue_type
|
93
|
-
project_issue_types = jira.project_issue_types(
|
106
|
+
project_issue_types = jira.project_issue_types()
|
94
107
|
project_issue_types_str = [i.name for i in project_issue_types]
|
95
108
|
if issue_type not in project_issue_types_str:
|
96
109
|
logging.error(
|
@@ -192,7 +205,6 @@ def validate_boards(
|
|
192
205
|
) -> bool:
|
193
206
|
error = False
|
194
207
|
jira_clients: dict[str, JiraClient] = {}
|
195
|
-
public_projects_cache: dict[str, list[str]] = {}
|
196
208
|
for board in jira_boards:
|
197
209
|
logging.debug(f"[{board.name}] checking ...")
|
198
210
|
if board.server.server_url not in jira_clients:
|
@@ -204,10 +216,6 @@ def validate_boards(
|
|
204
216
|
)
|
205
217
|
jira = jira_clients[board.server.server_url]
|
206
218
|
jira.project = board.name
|
207
|
-
if board.server.server_url not in public_projects_cache:
|
208
|
-
public_projects_cache[board.server.server_url] = jira.public_projects()
|
209
|
-
public_projects = public_projects_cache[board.server.server_url]
|
210
|
-
|
211
219
|
try:
|
212
220
|
error_flags = board_is_valid(
|
213
221
|
jira=jira,
|
@@ -215,7 +223,7 @@ def validate_boards(
|
|
215
223
|
default_issue_type=default_issue_type,
|
216
224
|
default_reopen_state=default_reopen_state,
|
217
225
|
jira_server_priorities={p.name: p.id for p in jira.priorities()},
|
218
|
-
public_projects=public_projects,
|
226
|
+
public_projects=jira.public_projects(),
|
219
227
|
)
|
220
228
|
match error_flags:
|
221
229
|
case 0:
|
@@ -256,13 +264,59 @@ def get_jira_boards(query_func: Callable) -> list[JiraBoardV1]:
|
|
256
264
|
]
|
257
265
|
|
258
266
|
|
259
|
-
def
|
267
|
+
def export_boards(boards: list[JiraBoardV1]) -> list[dict]:
|
268
|
+
return [board.dict() for board in boards]
|
269
|
+
|
270
|
+
|
271
|
+
def run(
|
272
|
+
dry_run: bool,
|
273
|
+
exit_on_permission_errors: bool,
|
274
|
+
enable_extended_early_exit: bool = False,
|
275
|
+
extended_early_exit_cache_ttl_seconds: int = 3600,
|
276
|
+
log_cached_log_output: bool = False,
|
277
|
+
) -> None:
|
278
|
+
gql_api = gql.get_api()
|
279
|
+
boards = get_jira_boards(query_func=gql_api.query)
|
280
|
+
runner_params: RunnerParams = dict(
|
281
|
+
exit_on_permission_errors=exit_on_permission_errors,
|
282
|
+
boards=boards,
|
283
|
+
)
|
284
|
+
if enable_extended_early_exit and get_feature_toggle_state(
|
285
|
+
"jira-permissions-validator-extended-early-exit",
|
286
|
+
default=True,
|
287
|
+
):
|
288
|
+
vault_settings = get_app_interface_vault_settings()
|
289
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
290
|
+
|
291
|
+
cache_source = CacheSource(
|
292
|
+
boards=export_boards(boards),
|
293
|
+
)
|
294
|
+
extended_early_exit_run(
|
295
|
+
integration=QONTRACT_INTEGRATION,
|
296
|
+
integration_version=QONTRACT_INTEGRATION_VERSION,
|
297
|
+
# don't use `dry_run` in the cache key because this is a read-only integration
|
298
|
+
dry_run=False,
|
299
|
+
cache_source=cache_source,
|
300
|
+
shard="",
|
301
|
+
ttl_seconds=extended_early_exit_cache_ttl_seconds,
|
302
|
+
logger=logging.getLogger(),
|
303
|
+
runner=runner,
|
304
|
+
runner_params=runner_params,
|
305
|
+
secret_reader=secret_reader,
|
306
|
+
log_cached_log_output=log_cached_log_output,
|
307
|
+
)
|
308
|
+
else:
|
309
|
+
runner(**runner_params)
|
310
|
+
|
311
|
+
|
312
|
+
def runner(
|
313
|
+
exit_on_permission_errors: bool, boards: list[JiraBoardV1]
|
314
|
+
) -> ExtendedEarlyExitRunnerResult:
|
260
315
|
gql_api = gql.get_api()
|
261
316
|
settings = get_jira_settings(gql_api=gql_api)
|
262
317
|
jiralert_settings = get_jiralert_settings(query_func=gql_api.query)
|
263
318
|
vault_settings = get_app_interface_vault_settings()
|
264
319
|
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
265
|
-
boards = get_jira_boards(query_func=gql_api.query)
|
266
320
|
|
267
321
|
with metrics.transactional_metrics("jira-boards") as metrics_container:
|
268
322
|
error = validate_boards(
|
@@ -278,8 +332,8 @@ def run(dry_run: bool, exit_on_permission_errors: bool) -> None:
|
|
278
332
|
if error:
|
279
333
|
sys.exit(ExitCodes.ERROR)
|
280
334
|
|
335
|
+
return ExtendedEarlyExitRunnerResult(payload=export_boards(boards), applied_count=0)
|
336
|
+
|
281
337
|
|
282
338
|
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
283
|
-
return {
|
284
|
-
"boards": gql.get_api().query(JIRA_BOARDS_DEFINITION)["jira_boards"],
|
285
|
-
}
|
339
|
+
return {"boards": export_boards(get_jira_boards(query_func=gql.get_api().query))}
|
reconcile/utils/jira_client.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import functools
|
3
4
|
import logging
|
4
5
|
from collections.abc import (
|
5
6
|
Iterable,
|
@@ -82,6 +83,11 @@ class JiraClient:
|
|
82
83
|
self.project = project
|
83
84
|
self.jira = jira_api
|
84
85
|
|
86
|
+
# some caches
|
87
|
+
self.priorities = functools.lru_cache(maxsize=None)(self._priorities)
|
88
|
+
self.public_projects = functools.lru_cache(maxsize=None)(self._public_projects)
|
89
|
+
self.my_permissions = functools.lru_cache(maxsize=None)(self._my_permissions)
|
90
|
+
|
85
91
|
def _deprecated_init(
|
86
92
|
self, jira_board: Mapping[str, Any], settings: Optional[Mapping]
|
87
93
|
) -> None:
|
@@ -174,11 +180,12 @@ class JiraClient:
|
|
174
180
|
)
|
175
181
|
return issue
|
176
182
|
|
183
|
+
def _my_permissions(self, project: str) -> dict[str, Any]:
|
184
|
+
return self.jira.my_permissions(projectKey=project)["permissions"]
|
185
|
+
|
177
186
|
def can_i(self, permission: str) -> bool:
|
178
187
|
return bool(
|
179
|
-
self.
|
180
|
-
permission
|
181
|
-
]["havePermission"]
|
188
|
+
self.my_permissions(project=self.project)[permission]["havePermission"]
|
182
189
|
)
|
183
190
|
|
184
191
|
def can_create_issues(self) -> bool:
|
@@ -187,10 +194,10 @@ class JiraClient:
|
|
187
194
|
def can_transition_issues(self) -> bool:
|
188
195
|
return self.can_i("TRANSITION_ISSUES")
|
189
196
|
|
190
|
-
def project_issue_types(self
|
197
|
+
def project_issue_types(self) -> list[IssueType]:
|
191
198
|
return [
|
192
199
|
IssueType(id=t.id, name=t.name, statuses=[s.name for s in t.statuses])
|
193
|
-
for t in self.jira.issue_types_for_project(project)
|
200
|
+
for t in self.jira.issue_types_for_project(self.project)
|
194
201
|
]
|
195
202
|
|
196
203
|
def security_levels(self) -> list[SecurityLevel]:
|
@@ -201,7 +208,7 @@ class JiraClient:
|
|
201
208
|
scheme = self.jira.project_issue_security_level_scheme(self.project)
|
202
209
|
return [SecurityLevel(id=level.id, name=level.name) for level in scheme.levels]
|
203
210
|
|
204
|
-
def
|
211
|
+
def _priorities(self) -> list[Priority]:
|
205
212
|
"""Return a list of all available Jira priorities."""
|
206
213
|
return [Priority(id=p.id, name=p.name) for p in self.jira.priorities()]
|
207
214
|
|
@@ -210,7 +217,7 @@ class JiraClient:
|
|
210
217
|
scheme = self.jira.project_priority_scheme(self.project)
|
211
218
|
return scheme.optionIds
|
212
219
|
|
213
|
-
def
|
220
|
+
def _public_projects(self) -> list[str]:
|
214
221
|
"""Return a list of all public available projects."""
|
215
222
|
if not self.server:
|
216
223
|
raise RuntimeError("JiraClient.server is not set.")
|
reconcile/utils/vcs.py
CHANGED
@@ -216,8 +216,13 @@ class VCS:
|
|
216
216
|
self._app_interface_api.close(mr)
|
217
217
|
|
218
218
|
def get_file_content_from_app_interface_master(self, file_path: str) -> str:
|
219
|
+
file_path = (
|
220
|
+
f"data/{file_path.lstrip('/')}"
|
221
|
+
if not file_path.startswith("data")
|
222
|
+
else file_path
|
223
|
+
)
|
219
224
|
return self._app_interface_api.project.files.get(
|
220
|
-
file_path=
|
225
|
+
file_path=file_path, ref="master"
|
221
226
|
).decode()
|
222
227
|
|
223
228
|
def get_open_app_interface_merge_requests(self) -> list[ProjectMergeRequest]:
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc746.dist-info → qontract_reconcile-0.10.1rc748.dist-info}/top_level.txt
RENAMED
File without changes
|