qontract-reconcile 0.10.1rc879__py3-none-any.whl → 0.10.1rc894__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.
Files changed (291) hide show
  1. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/RECORD +291 -284
  3. reconcile/acs_rbac.py +1 -2
  4. reconcile/aus/advanced_upgrade_service.py +14 -14
  5. reconcile/aus/aus_label_source.py +1 -2
  6. reconcile/aus/base.py +23 -26
  7. reconcile/aus/cluster_version_data.py +4 -4
  8. reconcile/aus/models.py +2 -3
  9. reconcile/aus/version_gate_approver.py +2 -6
  10. reconcile/aus/version_gates/__init__.py +1 -3
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +2 -3
  12. reconcile/aws_account_manager/integration.py +9 -14
  13. reconcile/aws_account_manager/reconciler.py +51 -1
  14. reconcile/aws_account_manager/utils.py +3 -0
  15. reconcile/aws_ami_cleanup/integration.py +3 -4
  16. reconcile/aws_iam_password_reset.py +2 -5
  17. reconcile/aws_version_sync/integration.py +2 -2
  18. reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
  19. reconcile/change_owners/approver.py +4 -5
  20. reconcile/change_owners/bundle.py +20 -22
  21. reconcile/change_owners/change_types.py +23 -24
  22. reconcile/change_owners/changes.py +13 -16
  23. reconcile/change_owners/decision.py +2 -5
  24. reconcile/change_owners/diff.py +11 -15
  25. reconcile/change_owners/self_service_roles.py +1 -2
  26. reconcile/change_owners/tester.py +7 -10
  27. reconcile/checkpoint.py +2 -5
  28. reconcile/cli.py +26 -12
  29. reconcile/closedbox_endpoint_monitoring_base.py +8 -11
  30. reconcile/cluster_deployment_mapper.py +2 -5
  31. reconcile/cna/assets/asset.py +4 -7
  32. reconcile/cna/assets/null.py +2 -5
  33. reconcile/cna/integration.py +2 -3
  34. reconcile/cna/state.py +2 -5
  35. reconcile/dashdotdb_base.py +8 -11
  36. reconcile/dashdotdb_cso.py +3 -6
  37. reconcile/dashdotdb_dora.py +10 -14
  38. reconcile/dashdotdb_dvo.py +10 -13
  39. reconcile/dashdotdb_slo.py +5 -8
  40. reconcile/database_access_manager.py +5 -6
  41. reconcile/dynatrace_token_provider/integration.py +3 -6
  42. reconcile/dynatrace_token_provider/integration_v2.py +20 -0
  43. reconcile/dynatrace_token_provider/meta.py +1 -0
  44. reconcile/external_resources/integration.py +1 -1
  45. reconcile/external_resources/manager.py +4 -4
  46. reconcile/external_resources/model.py +3 -3
  47. reconcile/external_resources/secrets_sync.py +5 -5
  48. reconcile/external_resources/state.py +5 -5
  49. reconcile/gabi_authorized_users.py +3 -6
  50. reconcile/gcr_mirror.py +1 -1
  51. reconcile/github_org.py +1 -3
  52. reconcile/github_repo_invites.py +2 -5
  53. reconcile/gitlab_housekeeping.py +7 -11
  54. reconcile/gitlab_labeler.py +1 -2
  55. reconcile/gitlab_members.py +2 -5
  56. reconcile/gitlab_permissions.py +1 -3
  57. reconcile/glitchtip/integration.py +5 -8
  58. reconcile/glitchtip_project_alerts/integration.py +57 -33
  59. reconcile/glitchtip_project_dsn/integration.py +8 -11
  60. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +6 -0
  61. reconcile/gql_definitions/fragments/aws_account_managed.py +8 -0
  62. reconcile/gql_definitions/glitchtip/glitchtip_project.py +4 -4
  63. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +27 -7
  64. reconcile/integrations_manager.py +5 -8
  65. reconcile/jenkins/types.py +5 -6
  66. reconcile/jenkins_job_builder.py +9 -12
  67. reconcile/jenkins_roles.py +1 -1
  68. reconcile/jira_watcher.py +2 -2
  69. reconcile/ldap_groups/integration.py +2 -5
  70. reconcile/ocm/types.py +21 -26
  71. reconcile/ocm_addons_upgrade_tests_trigger.py +3 -6
  72. reconcile/ocm_clusters.py +8 -8
  73. reconcile/ocm_internal_notifications/integration.py +1 -2
  74. reconcile/ocm_labels/integration.py +2 -5
  75. reconcile/ocm_machine_pools.py +11 -15
  76. reconcile/ocm_upgrade_scheduler_org_updater.py +2 -5
  77. reconcile/openshift_base.py +29 -30
  78. reconcile/openshift_groups.py +15 -20
  79. reconcile/openshift_namespace_labels.py +8 -14
  80. reconcile/openshift_namespaces.py +5 -8
  81. reconcile/openshift_network_policies.py +2 -4
  82. reconcile/openshift_resources_base.py +19 -29
  83. reconcile/openshift_saas_deploy.py +9 -10
  84. reconcile/openshift_saas_deploy_change_tester.py +7 -10
  85. reconcile/openshift_saas_deploy_trigger_base.py +4 -7
  86. reconcile/openshift_saas_deploy_trigger_cleaner.py +5 -8
  87. reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
  88. reconcile/openshift_saas_deploy_trigger_images.py +1 -2
  89. reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
  90. reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
  91. reconcile/openshift_tekton_resources.py +7 -11
  92. reconcile/openshift_upgrade_watcher.py +10 -13
  93. reconcile/openshift_users.py +8 -11
  94. reconcile/oum/base.py +3 -4
  95. reconcile/oum/labelset.py +1 -2
  96. reconcile/oum/metrics.py +2 -2
  97. reconcile/oum/models.py +1 -2
  98. reconcile/oum/standalone.py +2 -3
  99. reconcile/prometheus_rules_tester/integration.py +6 -9
  100. reconcile/quay_membership.py +1 -2
  101. reconcile/quay_mirror.py +12 -13
  102. reconcile/quay_mirror_org.py +10 -10
  103. reconcile/queries.py +4 -7
  104. reconcile/resource_scraper.py +3 -4
  105. reconcile/rhidp/common.py +2 -2
  106. reconcile/saas_auto_promotions_manager/integration.py +5 -6
  107. reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +1 -2
  108. reconcile/saas_auto_promotions_manager/publisher.py +5 -6
  109. reconcile/saas_auto_promotions_manager/subscriber.py +36 -15
  110. reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +8 -0
  111. reconcile/saas_file_validator.py +2 -5
  112. reconcile/signalfx_endpoint_monitoring.py +2 -5
  113. reconcile/skupper_network/integration.py +3 -6
  114. reconcile/skupper_network/models.py +3 -5
  115. reconcile/slack_base.py +4 -7
  116. reconcile/slack_usergroups.py +15 -17
  117. reconcile/sql_query.py +5 -9
  118. reconcile/status_board.py +4 -5
  119. reconcile/statuspage/atlassian.py +14 -15
  120. reconcile/statuspage/integrations/maintenances.py +3 -3
  121. reconcile/statuspage/page.py +8 -8
  122. reconcile/statuspage/state.py +4 -5
  123. reconcile/statuspage/status.py +7 -8
  124. reconcile/templating/lib/rendering.py +8 -8
  125. reconcile/templating/renderer.py +10 -11
  126. reconcile/templating/validator.py +4 -4
  127. reconcile/terraform_aws_route53.py +3 -6
  128. reconcile/terraform_cloudflare_dns.py +9 -12
  129. reconcile/terraform_cloudflare_resources.py +9 -11
  130. reconcile/terraform_cloudflare_users.py +8 -11
  131. reconcile/terraform_init/integration.py +2 -2
  132. reconcile/terraform_repo.py +11 -14
  133. reconcile/terraform_resources.py +20 -21
  134. reconcile/terraform_tgw_attachments.py +32 -36
  135. reconcile/terraform_users.py +6 -7
  136. reconcile/terraform_vpc_resources/integration.py +6 -6
  137. reconcile/test/conftest.py +7 -10
  138. reconcile/test/fixtures.py +1 -1
  139. reconcile/test/saas_auto_promotions_manager/conftest.py +3 -2
  140. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +2 -2
  141. reconcile/test/test_database_access_manager.py +3 -6
  142. reconcile/test/test_gitlab_labeler.py +2 -5
  143. reconcile/test/test_jump_host.py +5 -8
  144. reconcile/test/test_ocm_machine_pools.py +1 -4
  145. reconcile/test/test_openshift_base.py +3 -6
  146. reconcile/test/test_openshift_cluster_bots.py +5 -5
  147. reconcile/test/test_openshift_namespace_labels.py +2 -3
  148. reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +2 -2
  149. reconcile/test/test_saasherder.py +9 -12
  150. reconcile/test/test_slack_base.py +4 -6
  151. reconcile/test/test_status_board.py +4 -7
  152. reconcile/test/test_terraform_tgw_attachments.py +14 -20
  153. reconcile/typed_queries/alerting_services_settings.py +1 -2
  154. reconcile/typed_queries/app_interface_custom_messages.py +2 -3
  155. reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +1 -3
  156. reconcile/typed_queries/app_interface_repo_url.py +1 -2
  157. reconcile/typed_queries/app_interface_state_settings.py +1 -3
  158. reconcile/typed_queries/app_interface_vault_settings.py +1 -2
  159. reconcile/typed_queries/aws_vpc_requests.py +1 -3
  160. reconcile/typed_queries/aws_vpcs.py +1 -3
  161. reconcile/typed_queries/clusters.py +2 -4
  162. reconcile/typed_queries/clusters_minimal.py +1 -3
  163. reconcile/typed_queries/clusters_with_dms.py +1 -3
  164. reconcile/typed_queries/dynatrace_environments.py +14 -0
  165. reconcile/typed_queries/external_resources.py +3 -4
  166. reconcile/typed_queries/pagerduty_instances.py +1 -2
  167. reconcile/typed_queries/repos.py +2 -3
  168. reconcile/typed_queries/reserved_networks.py +1 -3
  169. reconcile/typed_queries/saas_files.py +49 -59
  170. reconcile/typed_queries/slo_documents.py +1 -3
  171. reconcile/typed_queries/status_board.py +3 -7
  172. reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
  173. reconcile/typed_queries/terraform_namespaces.py +1 -2
  174. reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +1 -3
  175. reconcile/utils/acs/base.py +2 -3
  176. reconcile/utils/acs/notifiers.py +3 -3
  177. reconcile/utils/acs/policies.py +3 -3
  178. reconcile/utils/aggregated_list.py +1 -1
  179. reconcile/utils/amtool.py +1 -2
  180. reconcile/utils/aws_api.py +28 -31
  181. reconcile/utils/aws_api_typed/account.py +23 -0
  182. reconcile/utils/aws_api_typed/api.py +20 -9
  183. reconcile/utils/binary.py +1 -3
  184. reconcile/utils/clusterhealth/providerbase.py +1 -2
  185. reconcile/utils/clusterhealth/telemeter.py +2 -2
  186. reconcile/utils/deadmanssnitch_api.py +1 -2
  187. reconcile/utils/disabled_integrations.py +4 -6
  188. reconcile/utils/environ.py +1 -1
  189. reconcile/utils/expiration.py +3 -7
  190. reconcile/utils/external_resource_spec.py +3 -4
  191. reconcile/utils/external_resources.py +4 -7
  192. reconcile/utils/filtering.py +1 -2
  193. reconcile/utils/git.py +3 -9
  194. reconcile/utils/git_secrets.py +5 -5
  195. reconcile/utils/github_api.py +5 -9
  196. reconcile/utils/gitlab_api.py +2 -3
  197. reconcile/utils/glitchtip/client.py +2 -4
  198. reconcile/utils/glitchtip/models.py +8 -11
  199. reconcile/utils/gql.py +26 -35
  200. reconcile/utils/grouping.py +1 -3
  201. reconcile/utils/imap_client.py +2 -5
  202. reconcile/utils/internal_groups/client.py +1 -2
  203. reconcile/utils/internal_groups/models.py +8 -9
  204. reconcile/utils/jenkins_api.py +4 -4
  205. reconcile/utils/jinja2/extensions.py +1 -1
  206. reconcile/utils/jinja2/filters.py +4 -4
  207. reconcile/utils/jinja2/utils.py +16 -16
  208. reconcile/utils/jira_client.py +10 -11
  209. reconcile/utils/jjb_client.py +14 -17
  210. reconcile/utils/jobcontroller/controller.py +5 -5
  211. reconcile/utils/jobcontroller/models.py +2 -2
  212. reconcile/utils/jsonpath.py +4 -5
  213. reconcile/utils/jump_host.py +7 -8
  214. reconcile/utils/keycloak.py +3 -7
  215. reconcile/utils/ldap_client.py +2 -3
  216. reconcile/utils/lean_terraform_client.py +13 -17
  217. reconcile/utils/membershipsources/app_interface_resolver.py +1 -1
  218. reconcile/utils/membershipsources/models.py +19 -22
  219. reconcile/utils/metrics.py +13 -15
  220. reconcile/utils/mr/base.py +7 -11
  221. reconcile/utils/mr/glitchtip_access_reporter.py +2 -2
  222. reconcile/utils/mr/notificator.py +1 -2
  223. reconcile/utils/oc.py +38 -38
  224. reconcile/utils/oc_connection_parameters.py +24 -25
  225. reconcile/utils/oc_filters.py +2 -3
  226. reconcile/utils/oc_map.py +9 -15
  227. reconcile/utils/ocm/addons.py +7 -10
  228. reconcile/utils/ocm/base.py +38 -39
  229. reconcile/utils/ocm/clusters.py +6 -9
  230. reconcile/utils/ocm/label_sources.py +1 -2
  231. reconcile/utils/ocm/labels.py +3 -6
  232. reconcile/utils/ocm/ocm.py +11 -14
  233. reconcile/utils/ocm/products.py +1 -3
  234. reconcile/utils/ocm/search_filters.py +16 -17
  235. reconcile/utils/ocm/service_log.py +2 -3
  236. reconcile/utils/ocm/sre_capability_labels.py +4 -8
  237. reconcile/utils/ocm/subscriptions.py +1 -3
  238. reconcile/utils/ocm/syncsets.py +2 -4
  239. reconcile/utils/ocm/upgrades.py +5 -9
  240. reconcile/utils/ocm_base_client.py +13 -16
  241. reconcile/utils/openshift_resource.py +5 -11
  242. reconcile/utils/output.py +2 -3
  243. reconcile/utils/pagerduty_api.py +4 -5
  244. reconcile/utils/prometheus.py +2 -2
  245. reconcile/utils/promotion_state.py +4 -5
  246. reconcile/utils/promtool.py +2 -8
  247. reconcile/utils/quay_api.py +12 -22
  248. reconcile/utils/raw_github_api.py +3 -5
  249. reconcile/utils/rosa/rosa_cli.py +6 -6
  250. reconcile/utils/rosa/session.py +6 -7
  251. reconcile/utils/runtime/desired_state_diff.py +3 -8
  252. reconcile/utils/runtime/environment.py +4 -7
  253. reconcile/utils/runtime/integration.py +4 -4
  254. reconcile/utils/runtime/meta.py +1 -2
  255. reconcile/utils/runtime/runner.py +7 -10
  256. reconcile/utils/runtime/sharding.py +22 -27
  257. reconcile/utils/saasherder/interfaces.py +63 -69
  258. reconcile/utils/saasherder/models.py +30 -35
  259. reconcile/utils/saasherder/saasherder.py +39 -54
  260. reconcile/utils/secret_reader.py +17 -19
  261. reconcile/utils/slack_api.py +15 -17
  262. reconcile/utils/smtp_client.py +1 -2
  263. reconcile/utils/sqs_gateway.py +1 -3
  264. reconcile/utils/state.py +1 -2
  265. reconcile/utils/terraform/config_client.py +4 -5
  266. reconcile/utils/terraform_client.py +12 -8
  267. reconcile/utils/terrascript/cloudflare_client.py +4 -10
  268. reconcile/utils/terrascript/cloudflare_resources.py +10 -13
  269. reconcile/utils/terrascript/models.py +2 -3
  270. reconcile/utils/terrascript/resources.py +1 -2
  271. reconcile/utils/terrascript_aws_client.py +50 -38
  272. reconcile/utils/unleash/client.py +4 -7
  273. reconcile/utils/unleash/server.py +2 -2
  274. reconcile/utils/vault.py +8 -11
  275. reconcile/utils/vaultsecretref.py +2 -3
  276. reconcile/utils/vcs.py +7 -8
  277. reconcile/vault_replication.py +4 -8
  278. reconcile/vpc_peerings_validator.py +4 -9
  279. release/version.py +6 -7
  280. tools/app_interface_reporter.py +2 -2
  281. tools/cli_commands/gpg_encrypt.py +3 -6
  282. tools/cli_commands/systems_and_tools.py +4 -7
  283. tools/qontract_cli.py +105 -17
  284. tools/saas_promotion_state/__init__.py +0 -0
  285. tools/saas_promotion_state/saas_promotion_state.py +105 -0
  286. tools/template_validation.py +1 -1
  287. tools/test/conftest.py +45 -6
  288. tools/test/test_saas_promotion_state.py +187 -0
  289. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/WHEEL +0 -0
  290. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/entry_points.txt +0 -0
  291. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/top_level.txt +0 -0
reconcile/github_org.py CHANGED
@@ -438,9 +438,7 @@ def run(dry_run):
438
438
 
439
439
  assert (
440
440
  current_orgs == desired_orgs
441
- ), "Current orgs ({}) don't match desired orgs ({})".format(
442
- current_orgs, desired_orgs
443
- )
441
+ ), f"Current orgs ({current_orgs}) don't match desired orgs ({desired_orgs})"
444
442
 
445
443
  # Calculate diff
446
444
  diff = current_state.diff(desired_state)
@@ -5,10 +5,7 @@ from collections.abc import (
5
5
  Mapping,
6
6
  )
7
7
  from dataclasses import dataclass
8
- from typing import (
9
- Any,
10
- Optional,
11
- )
8
+ from typing import Any
12
9
 
13
10
  from reconcile import queries
14
11
  from reconcile.utils import (
@@ -27,7 +24,7 @@ class CodeComponents:
27
24
 
28
25
 
29
26
  def _parse_code_components(
30
- raw: Optional[Iterable[Mapping[str, Any]]],
27
+ raw: Iterable[Mapping[str, Any]] | None,
31
28
  ) -> CodeComponents:
32
29
  urls = set()
33
30
  known_orgs = set()
@@ -9,11 +9,7 @@ from datetime import (
9
9
  timedelta,
10
10
  )
11
11
  from operator import itemgetter
12
- from typing import (
13
- Any,
14
- Optional,
15
- Union,
16
- )
12
+ from typing import Any
17
13
 
18
14
  import gitlab
19
15
  from gitlab.v4.objects import (
@@ -178,7 +174,7 @@ def close_item(
178
174
  gl: GitLabApi,
179
175
  enable_closing: bool,
180
176
  item_type: str,
181
- item: Union[ProjectIssue, ProjectMergeRequest],
177
+ item: ProjectIssue | ProjectMergeRequest,
182
178
  ):
183
179
  if enable_closing:
184
180
  logging.info([
@@ -239,7 +235,7 @@ def handle_stale_items(
239
235
  cancel_notes = [
240
236
  n
241
237
  for n in item.notes.list()
242
- if n.attributes.get("body") == "/{} cancel".format(LABEL)
238
+ if n.attributes.get("body") == f"/{LABEL} cancel"
243
239
  ]
244
240
  if not cancel_notes:
245
241
  continue
@@ -280,7 +276,7 @@ def is_rebased(mr, gl: GitLabApi) -> bool:
280
276
  def get_merge_requests(
281
277
  dry_run: bool,
282
278
  gl: GitLabApi,
283
- users_allowed_to_label: Optional[Iterable[str]] = None,
279
+ users_allowed_to_label: Iterable[str] | None = None,
284
280
  ) -> list[dict[str, Any]]:
285
281
  mrs = gl.get_merge_requests(state=MRState.OPENED)
286
282
  return preprocess_merge_requests(
@@ -295,7 +291,7 @@ def preprocess_merge_requests(
295
291
  dry_run: bool,
296
292
  gl: GitLabApi,
297
293
  project_merge_requests: list[ProjectMergeRequest],
298
- users_allowed_to_label: Optional[Iterable[str]] = None,
294
+ users_allowed_to_label: Iterable[str] | None = None,
299
295
  ) -> list[dict[str, Any]]:
300
296
  results = []
301
297
  for mr in project_merge_requests:
@@ -434,7 +430,7 @@ def rebase_merge_requests(
434
430
  rebases += 1
435
431
  rebased_merge_requests.labels(mr.target_project_id).inc()
436
432
  except gitlab.exceptions.GitlabMRRebaseError as e:
437
- logging.error("unable to rebase {}: {}".format(mr.iid, e))
433
+ logging.error(f"unable to rebase {mr.iid}: {e}")
438
434
  else:
439
435
  logging.info([
440
436
  "rebase",
@@ -538,7 +534,7 @@ def merge_merge_requests(
538
534
  return
539
535
  merges += 1
540
536
  except gitlab.exceptions.GitlabMRClosedError as e:
541
- logging.error("unable to merge {}: {}".format(mr.iid, e))
537
+ logging.error(f"unable to merge {mr.iid}: {e}")
542
538
 
543
539
 
544
540
  def get_app_sre_usernames(gl: GitLabApi) -> set[str]:
@@ -4,7 +4,6 @@ from collections.abc import (
4
4
  Iterable,
5
5
  Set,
6
6
  )
7
- from typing import Optional
8
7
 
9
8
  from reconcile import queries
10
9
  from reconcile.gitlab_housekeeping import (
@@ -48,7 +47,7 @@ def get_parents_list() -> set[str]:
48
47
 
49
48
  def guess_onboarding_status(
50
49
  changed_paths: Iterable[str], apps: dict[str, dict], parent_apps: set[str]
51
- ) -> Optional[str]:
50
+ ) -> str | None:
52
51
  """
53
52
  Guess the service name of a given MR from the changed paths of the
54
53
  MR. This will allow to add the onboarding status to the MR's as label
@@ -1,10 +1,7 @@
1
1
  import enum
2
2
  import logging
3
3
  from collections.abc import Callable
4
- from typing import (
5
- Any,
6
- Optional,
7
- )
4
+ from typing import Any
8
5
 
9
6
  from pydantic import BaseModel
10
7
 
@@ -203,7 +200,7 @@ def get_gitlab_instance(query_func: Callable) -> GitlabInstanceV1:
203
200
  @defer
204
201
  def run(
205
202
  dry_run: bool,
206
- defer: Optional[Callable] = None,
203
+ defer: Callable | None = None,
207
204
  ) -> None:
208
205
  gqlapi = gql.get_api()
209
206
  # queries
@@ -21,9 +21,7 @@ def get_members_to_add(repo, gl, app_sre):
21
21
  if maintainers is None:
22
22
  return []
23
23
  if gl.user.username not in maintainers:
24
- logging.error(
25
- "'{}' is not shared with {} as 'Maintainer'".format(repo, gl.user.username)
26
- )
24
+ logging.error(f"'{repo}' is not shared with {gl.user.username} as 'Maintainer'")
27
25
  return []
28
26
  members_to_add = [
29
27
  {"user": u, "repo": repo} for u in app_sre if u.username not in maintainers
@@ -3,10 +3,7 @@ from collections.abc import (
3
3
  Iterable,
4
4
  Sequence,
5
5
  )
6
- from typing import (
7
- Any,
8
- Optional,
9
- )
6
+ from typing import Any
10
7
 
11
8
  from reconcile.glitchtip.reconciler import GlitchtipReconciler
12
9
  from reconcile.gql_definitions.glitchtip.glitchtip_instance import (
@@ -19,7 +16,7 @@ from reconcile.gql_definitions.glitchtip.glitchtip_project import (
19
16
  DEFINITION as GLITCHTIP_PROJECT_DEFINITION,
20
17
  )
21
18
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
22
- GlitchtipProjectsV1,
19
+ GlitchtipProjectV1,
23
20
  RoleV1,
24
21
  )
25
22
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
@@ -87,7 +84,7 @@ def fetch_current_state(
87
84
 
88
85
 
89
86
  def fetch_desired_state(
90
- glitchtip_projects: Sequence[GlitchtipProjectsV1],
87
+ glitchtip_projects: Sequence[GlitchtipProjectV1],
91
88
  mail_domain: str,
92
89
  internal_groups_client: InternalGroupsClient,
93
90
  ) -> list[Organization]:
@@ -143,7 +140,7 @@ def fetch_desired_state(
143
140
  return list(organizations.values())
144
141
 
145
142
 
146
- def get_glitchtip_projects(query_func: Callable) -> list[GlitchtipProjectsV1]:
143
+ def get_glitchtip_projects(query_func: Callable) -> list[GlitchtipProjectV1]:
147
144
  glitchtip_projects = (
148
145
  glitchtip_project_query(query_func=query_func).glitchtip_projects or []
149
146
  )
@@ -172,7 +169,7 @@ def get_internal_groups_client(
172
169
 
173
170
  @defer
174
171
  def run(
175
- dry_run: bool, instance: Optional[str] = None, defer: Optional[Callable] = None
172
+ dry_run: bool, instance: str | None = None, defer: Callable | None = None
176
173
  ) -> None:
177
174
  gqlapi = gql.get_api()
178
175
  vault_settings = get_app_interface_vault_settings()
@@ -4,10 +4,7 @@ from collections.abc import (
4
4
  Callable,
5
5
  Iterable,
6
6
  )
7
- from typing import (
8
- Any,
9
- Optional,
10
- )
7
+ from typing import Any
11
8
  from urllib.parse import urlencode
12
9
 
13
10
  from reconcile import jira_permissions_validator
@@ -18,7 +15,7 @@ from reconcile.gql_definitions.glitchtip_project_alerts.glitchtip_project import
18
15
  GlitchtipProjectAlertRecipientEmailV1,
19
16
  GlitchtipProjectAlertRecipientV1,
20
17
  GlitchtipProjectAlertRecipientWebhookV1,
21
- GlitchtipProjectsV1,
18
+ GlitchtipProjectV1,
22
19
  )
23
20
  from reconcile.gql_definitions.glitchtip_project_alerts.glitchtip_project import (
24
21
  query as glitchtip_project_query,
@@ -46,7 +43,7 @@ ProjectStates = dict[str, Project]
46
43
 
47
44
 
48
45
  class GlitchtipProjectAlertsIntegrationParams(PydanticRunParams):
49
- instance: Optional[str] = None
46
+ instance: str | None = None
50
47
 
51
48
 
52
49
  def webhook_urls_are_unique(alerts: Iterable[ProjectAlert]) -> bool:
@@ -70,10 +67,10 @@ class GlitchtipProjectAlertsIntegration(
70
67
  def name(self) -> str:
71
68
  return QONTRACT_INTEGRATION
72
69
 
73
- def get_early_exit_desired_state(self) -> Optional[dict[str, Any]]:
70
+ def get_early_exit_desired_state(self) -> dict[str, Any] | None:
74
71
  return {"projects": [c.dict() for c in self.get_projects(gql.get_api().query)]}
75
72
 
76
- def get_projects(self, query_func: Callable) -> list[GlitchtipProjectsV1]:
73
+ def get_projects(self, query_func: Callable) -> list[GlitchtipProjectV1]:
77
74
  return glitchtip_project_query(query_func=query_func).glitchtip_projects or []
78
75
 
79
76
  def _build_project_alert_recipient(
@@ -95,7 +92,7 @@ class GlitchtipProjectAlertsIntegration(
95
92
 
96
93
  def fetch_desired_state(
97
94
  self,
98
- glitchtip_projects: Iterable[GlitchtipProjectsV1],
95
+ glitchtip_projects: Iterable[GlitchtipProjectV1],
99
96
  gjb_alert_url: str | None,
100
97
  gjb_token: str | None,
101
98
  ) -> list[Organization]:
@@ -123,30 +120,16 @@ class GlitchtipProjectAlertsIntegration(
123
120
  )
124
121
  )
125
122
  if glitchtip_project.jira and gjb_alert_url:
126
- jira_project_key = None
127
- if glitchtip_project.jira.project:
128
- jira_project_key = glitchtip_project.jira.project
129
- elif (
130
- glitchtip_project.jira.board
131
- and integration_is_enabled(
132
- QONTRACT_INTEGRATION, glitchtip_project.jira.board
133
- )
134
- and integration_is_enabled(
135
- jira_permissions_validator.QONTRACT_INTEGRATION,
136
- glitchtip_project.jira.board,
137
- )
138
- ):
139
- jira_project_key = glitchtip_project.jira.board.name
123
+ params: dict[str, str | list[str]] = {}
124
+ token_params = {"token": gjb_token} if gjb_token else {}
125
+ alert_labels = glitchtip_project.jira.labels or []
140
126
 
141
- if jira_project_key:
142
- params: dict[str, str | list] = {}
143
- if gjb_token:
144
- params["token"] = gjb_token
145
- if glitchtip_project.jira.labels:
146
- params["labels"] = glitchtip_project.jira.labels
147
- url = (
148
- f"{gjb_alert_url}/{jira_project_key}?{urlencode(params, True)}"
149
- )
127
+ if glitchtip_project.jira.project:
128
+ params = {
129
+ "labels": alert_labels,
130
+ "components": glitchtip_project.jira.components or [],
131
+ } | token_params
132
+ url = f"{gjb_alert_url}/{glitchtip_project.jira.project}?{urlencode(params, True)}"
150
133
  alerts.append(
151
134
  ProjectAlert(
152
135
  name=GJB_ALERT_NAME,
@@ -160,6 +143,47 @@ class GlitchtipProjectAlertsIntegration(
160
143
  ],
161
144
  )
162
145
  )
146
+
147
+ elif (
148
+ glitchtip_project.jira.escalation_policy
149
+ and glitchtip_project.jira.escalation_policy.channels.jira_board
150
+ ):
151
+ # definition via escalation policy
152
+ channels = glitchtip_project.jira.escalation_policy.channels
153
+ for board in channels.jira_board:
154
+ if not integration_is_enabled(
155
+ QONTRACT_INTEGRATION, board
156
+ ) or not integration_is_enabled(
157
+ jira_permissions_validator.QONTRACT_INTEGRATION, board
158
+ ):
159
+ continue
160
+ params = {
161
+ "labels": alert_labels + (channels.jira_labels or []),
162
+ "components": [channels.jira_component]
163
+ if channels.jira_component
164
+ else [],
165
+ } | token_params
166
+ if board.issue_type:
167
+ params["issue_type"] = board.issue_type
168
+ url = f"{gjb_alert_url}/{board.name}?{urlencode(params, True)}"
169
+ alerts.append(
170
+ ProjectAlert(
171
+ name=GJB_ALERT_NAME,
172
+ timespan_minutes=1,
173
+ quantity=1,
174
+ recipients=[
175
+ ProjectAlertRecipient(
176
+ recipient_type=RecipientType.WEBHOOK,
177
+ url=url,
178
+ )
179
+ ],
180
+ )
181
+ )
182
+ else:
183
+ raise ValueError(
184
+ "Jira integration requires either project or escalation policy to be set"
185
+ )
186
+
163
187
  # check for duplicates
164
188
  if not webhook_urls_are_unique(alerts):
165
189
  raise ValueError(
@@ -264,7 +288,7 @@ class GlitchtipProjectAlertsIntegration(
264
288
  glitchtip_instances = glitchtip_instance_query(
265
289
  query_func=gqlapi.query
266
290
  ).instances
267
- glitchtip_projects_by_instance: dict[str, list[GlitchtipProjectsV1]] = (
291
+ glitchtip_projects_by_instance: dict[str, list[GlitchtipProjectV1]] = (
268
292
  defaultdict(list)
269
293
  )
270
294
  for glitchtip_project in self.get_projects(query_func=gqlapi.query):
@@ -3,10 +3,7 @@ from collections.abc import (
3
3
  Callable,
4
4
  Iterable,
5
5
  )
6
- from typing import (
7
- Any,
8
- Optional,
9
- )
6
+ from typing import Any
10
7
 
11
8
  from sretoolbox.utils import threaded
12
9
 
@@ -20,7 +17,7 @@ from reconcile.gql_definitions.glitchtip.glitchtip_instance import (
20
17
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
21
18
  DEFINITION as GLITCHTIP_PROJECT_DEFINITION,
22
19
  )
23
- from reconcile.gql_definitions.glitchtip.glitchtip_project import GlitchtipProjectsV1
20
+ from reconcile.gql_definitions.glitchtip.glitchtip_project import GlitchtipProjectV1
24
21
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
25
22
  query as glitchtip_project_query,
26
23
  )
@@ -73,7 +70,7 @@ def glitchtip_project_dsn_secret(project: Project, key: ProjectKey) -> dict[str,
73
70
 
74
71
 
75
72
  def fetch_current_state(
76
- project: GlitchtipProjectsV1,
73
+ project: GlitchtipProjectV1,
77
74
  oc_map: OCMap,
78
75
  ri: ResourceInventory,
79
76
  ) -> None:
@@ -110,7 +107,7 @@ def fetch_current_state(
110
107
 
111
108
 
112
109
  def fetch_desired_state(
113
- glitchtip_projects: Iterable[GlitchtipProjectsV1],
110
+ glitchtip_projects: Iterable[GlitchtipProjectV1],
114
111
  ri: ResourceInventory,
115
112
  glitchtip_client: GlitchtipClient,
116
113
  ) -> None:
@@ -145,7 +142,7 @@ def fetch_desired_state(
145
142
  )
146
143
 
147
144
 
148
- def projects_query(query_func: Callable) -> list[GlitchtipProjectsV1]:
145
+ def projects_query(query_func: Callable) -> list[GlitchtipProjectV1]:
149
146
  glitchtip_projects = []
150
147
  for project in (
151
148
  glitchtip_project_query(query_func=query_func).glitchtip_projects or []
@@ -169,10 +166,10 @@ def projects_query(query_func: Callable) -> list[GlitchtipProjectsV1]:
169
166
  def run(
170
167
  dry_run: bool,
171
168
  thread_pool_size: int = 10,
172
- internal: Optional[bool] = None,
169
+ internal: bool | None = None,
173
170
  use_jump_host: bool = True,
174
- instance: Optional[str] = None,
175
- defer: Optional[Callable] = None,
171
+ instance: str | None = None,
172
+ defer: Callable | None = None,
176
173
  ) -> None:
177
174
  # settings
178
175
  vault_settings = get_app_interface_vault_settings()
@@ -39,6 +39,12 @@ fragment AWSAccountManaged on AWSAccount_v1 {
39
39
  value
40
40
  }
41
41
  }
42
+ securityContact {
43
+ name
44
+ title
45
+ email
46
+ phoneNumber
47
+ }
42
48
  }
43
49
 
44
50
  fragment VaultSecret on VaultSecret_v1 {
@@ -40,6 +40,13 @@ class AWSQuotaLimitsV1(ConfiguredBaseModel):
40
40
  quotas: list[AWSQuotaV1] = Field(..., alias="quotas")
41
41
 
42
42
 
43
+ class AWSContactV1(ConfiguredBaseModel):
44
+ name: str = Field(..., alias="name")
45
+ title: Optional[str] = Field(..., alias="title")
46
+ email: str = Field(..., alias="email")
47
+ phone_number: str = Field(..., alias="phoneNumber")
48
+
49
+
43
50
  class AWSAccountManaged(ConfiguredBaseModel):
44
51
  name: str = Field(..., alias="name")
45
52
  uid: str = Field(..., alias="uid")
@@ -47,3 +54,4 @@ class AWSAccountManaged(ConfiguredBaseModel):
47
54
  premium_support: bool = Field(..., alias="premiumSupport")
48
55
  organization: Optional[AWSOrganizationV1] = Field(..., alias="organization")
49
56
  quota_limits: Optional[list[AWSQuotaLimitsV1]] = Field(..., alias="quotaLimits")
57
+ security_contact: Optional[AWSContactV1] = Field(..., alias="securityContact")
@@ -143,7 +143,7 @@ class GlitchtipInstanceV1(ConfiguredBaseModel):
143
143
  name: str = Field(..., alias="name")
144
144
 
145
145
 
146
- class GlitchtipProjectsV1_GlitchtipOrganizationV1(ConfiguredBaseModel):
146
+ class GlitchtipProjectV1_GlitchtipOrganizationV1(ConfiguredBaseModel):
147
147
  name: str = Field(..., alias="name")
148
148
  instance: GlitchtipInstanceV1 = Field(..., alias="instance")
149
149
  owners: Optional[list[str]] = Field(..., alias="owners")
@@ -180,19 +180,19 @@ class AppV1(ConfiguredBaseModel):
180
180
  path: str = Field(..., alias="path")
181
181
 
182
182
 
183
- class GlitchtipProjectsV1(ConfiguredBaseModel):
183
+ class GlitchtipProjectV1(ConfiguredBaseModel):
184
184
  name: str = Field(..., alias="name")
185
185
  platform: str = Field(..., alias="platform")
186
186
  project_id: Optional[str] = Field(..., alias="projectId")
187
187
  event_throttle_rate: Optional[int] = Field(..., alias="eventThrottleRate")
188
188
  teams: list[GlitchtipTeamV1] = Field(..., alias="teams")
189
- organization: GlitchtipProjectsV1_GlitchtipOrganizationV1 = Field(..., alias="organization")
189
+ organization: GlitchtipProjectV1_GlitchtipOrganizationV1 = Field(..., alias="organization")
190
190
  namespaces: list[NamespaceV1] = Field(..., alias="namespaces")
191
191
  app: Optional[AppV1] = Field(..., alias="app")
192
192
 
193
193
 
194
194
  class ProjectsQueryData(ConfiguredBaseModel):
195
- glitchtip_projects: Optional[list[GlitchtipProjectsV1]] = Field(..., alias="glitchtip_projects")
195
+ glitchtip_projects: Optional[list[GlitchtipProjectV1]] = Field(..., alias="glitchtip_projects")
196
196
 
197
197
 
198
198
  def query(query_func: Callable, **kwargs: Any) -> ProjectsQueryData:
@@ -58,10 +58,18 @@ query GlitchtipProjectsWithAlerts {
58
58
  }
59
59
  jira {
60
60
  project
61
- board {
62
- name
63
- disable {
64
- integrations
61
+ components
62
+ escalationPolicy {
63
+ channels {
64
+ jiraBoard {
65
+ name
66
+ issueType
67
+ disable {
68
+ integrations
69
+ }
70
+ }
71
+ jiraComponent
72
+ jiraLabels
65
73
  }
66
74
  }
67
75
  labels
@@ -113,16 +121,28 @@ class DisableJiraBoardAutomationsV1(ConfiguredBaseModel):
113
121
 
114
122
  class JiraBoardV1(ConfiguredBaseModel):
115
123
  name: str = Field(..., alias="name")
124
+ issue_type: Optional[str] = Field(..., alias="issueType")
116
125
  disable: Optional[DisableJiraBoardAutomationsV1] = Field(..., alias="disable")
117
126
 
118
127
 
128
+ class AppEscalationPolicyChannelsV1(ConfiguredBaseModel):
129
+ jira_board: list[JiraBoardV1] = Field(..., alias="jiraBoard")
130
+ jira_component: Optional[str] = Field(..., alias="jiraComponent")
131
+ jira_labels: Optional[list[str]] = Field(..., alias="jiraLabels")
132
+
133
+
134
+ class AppEscalationPolicyV1(ConfiguredBaseModel):
135
+ channels: AppEscalationPolicyChannelsV1 = Field(..., alias="channels")
136
+
137
+
119
138
  class GlitchtipProjectJiraV1(ConfiguredBaseModel):
120
139
  project: Optional[str] = Field(..., alias="project")
121
- board: Optional[JiraBoardV1] = Field(..., alias="board")
140
+ components: Optional[list[str]] = Field(..., alias="components")
141
+ escalation_policy: Optional[AppEscalationPolicyV1] = Field(..., alias="escalationPolicy")
122
142
  labels: Optional[list[str]] = Field(..., alias="labels")
123
143
 
124
144
 
125
- class GlitchtipProjectsV1(ConfiguredBaseModel):
145
+ class GlitchtipProjectV1(ConfiguredBaseModel):
126
146
  name: str = Field(..., alias="name")
127
147
  project_id: Optional[str] = Field(..., alias="projectId")
128
148
  organization: GlitchtipOrganizationV1 = Field(..., alias="organization")
@@ -131,7 +151,7 @@ class GlitchtipProjectsV1(ConfiguredBaseModel):
131
151
 
132
152
 
133
153
  class GlitchtipProjectsWithAlertsQueryData(ConfiguredBaseModel):
134
- glitchtip_projects: Optional[list[GlitchtipProjectsV1]] = Field(..., alias="glitchtip_projects")
154
+ glitchtip_projects: Optional[list[GlitchtipProjectV1]] = Field(..., alias="glitchtip_projects")
135
155
 
136
156
 
137
157
  def query(query_func: Callable, **kwargs: Any) -> GlitchtipProjectsWithAlertsQueryData:
@@ -6,10 +6,7 @@ from collections.abc import (
6
6
  Mapping,
7
7
  Sequence,
8
8
  )
9
- from typing import (
10
- Any,
11
- Optional,
12
- )
9
+ from typing import Any
13
10
 
14
11
  from github import Github
15
12
  from pydantic import BaseModel
@@ -76,7 +73,7 @@ def collect_parameters(
76
73
  environment: EnvironmentV1,
77
74
  upstream: str,
78
75
  image: str,
79
- image_tag_from_ref: Optional[Mapping[str, str]],
76
+ image_tag_from_ref: Mapping[str, str] | None,
80
77
  ) -> dict[str, Any]:
81
78
  parameters: dict[str, Any] = {}
82
79
  if environment.parameters:
@@ -180,7 +177,7 @@ def construct_oc_resources(
180
177
  integrations_environment: IntegrationsEnvironment,
181
178
  upstream: str,
182
179
  image: str,
183
- image_tag_from_ref: Optional[Mapping[str, str]],
180
+ image_tag_from_ref: Mapping[str, str] | None,
184
181
  ) -> list[OpenshiftResource]:
185
182
  # Generate the openshift template with the helm chart. The resulting template
186
183
  # contains all the integrations in the environment
@@ -213,7 +210,7 @@ def fetch_desired_state(
213
210
  ri: ResourceInventory,
214
211
  upstream: str,
215
212
  image: str,
216
- image_tag_from_ref: Optional[Mapping[str, str]],
213
+ image_tag_from_ref: Mapping[str, str] | None,
217
214
  ):
218
215
  for ie in integrations_environments:
219
216
  oc_resources = construct_oc_resources(ie, upstream, image, image_tag_from_ref)
@@ -224,7 +221,7 @@ def fetch_desired_state(
224
221
 
225
222
 
226
223
  def filter_integrations(
227
- integrations: Iterable[IntegrationV1], upstream: Optional[str] = None
224
+ integrations: Iterable[IntegrationV1], upstream: str | None = None
228
225
  ) -> list[IntegrationV1]:
229
226
  if upstream is None:
230
227
  return list(integrations)
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import Enum
4
- from typing import Optional
5
4
 
6
5
  from pydantic import (
7
6
  BaseModel,
@@ -22,11 +21,11 @@ class SSHHostKeyVerificationStrategy(Enum):
22
21
 
23
22
  class SSHConnector(BaseModel):
24
23
  credentials_id: str = Field(..., alias="credentialsId")
25
- launch_timeout_seconds: Optional[int] = Field(None, alias="launchTimeoutSeconds")
26
- max_num_retries: Optional[int] = Field(None, alias="maxNumRetries")
27
- retry_wait_time: Optional[int] = Field(None, alias="retryWaitTime")
28
- port: Optional[int] = 22
29
- jvm_options: Optional[str] = Field(None, alias="jvmOptions")
24
+ launch_timeout_seconds: int | None = Field(None, alias="launchTimeoutSeconds")
25
+ max_num_retries: int | None = Field(None, alias="maxNumRetries")
26
+ retry_wait_time: int | None = Field(None, alias="retryWaitTime")
27
+ port: int | None = 22
28
+ jvm_options: str | None = Field(None, alias="jvmOptions")
30
29
  ssh_host_key_verification_strategy: SSHHostKeyVerificationStrategy = Field(
31
30
  SSHHostKeyVerificationStrategy.NON_VERIFYING_KEY_VERIFICATION_STRATEGY,
32
31
  alias="sshHostKeyVerificationStrategy",
@@ -1,10 +1,7 @@
1
1
  import logging
2
2
  import sys
3
3
  from collections.abc import Callable
4
- from typing import (
5
- Any,
6
- Optional,
7
- )
4
+ from typing import Any
8
5
 
9
6
  from reconcile import queries
10
7
  from reconcile.utils.defer import defer
@@ -20,7 +17,7 @@ GENERATE_TYPE = ["jobs", "views"]
20
17
 
21
18
 
22
19
  def collect_configs(
23
- instance_name: Optional[str], config_name: Optional[str]
20
+ instance_name: str | None, config_name: str | None
24
21
  ) -> list[dict[str, Any]]:
25
22
  configs = queries.get_jenkins_configs()
26
23
  if instance_name is not None:
@@ -41,8 +38,8 @@ def collect_configs(
41
38
 
42
39
  def init_jjb(
43
40
  secret_reader: SecretReaderBase,
44
- instance_name: Optional[str] = None,
45
- config_name: Optional[str] = None,
41
+ instance_name: str | None = None,
42
+ config_name: str | None = None,
46
43
  print_only: bool = False,
47
44
  ) -> JJB:
48
45
  configs = collect_configs(instance_name, config_name)
@@ -66,7 +63,7 @@ def validate_repos_and_admins(jjb: JJB):
66
63
  )
67
64
  unknown_admins = [a for a in jjb_admins if a not in github_usernames]
68
65
  for a in unknown_admins:
69
- logging.warning("admin is missing from users: {}".format(a))
66
+ logging.warning(f"admin is missing from users: {a}")
70
67
  if missing_repos:
71
68
  sys.exit(1)
72
69
 
@@ -76,10 +73,10 @@ def run(
76
73
  dry_run: bool,
77
74
  io_dir: str = "throughput/",
78
75
  print_only: bool = False,
79
- config_name: Optional[str] = None,
80
- job_name: Optional[str] = None,
81
- instance_name: Optional[str] = None,
82
- defer: Optional[Callable] = None,
76
+ config_name: str | None = None,
77
+ job_name: str | None = None,
78
+ instance_name: str | None = None,
79
+ defer: Callable | None = None,
83
80
  ) -> None:
84
81
  if not print_only and config_name is not None:
85
82
  raise Exception("--config-name must works with --print-only mode")
@@ -170,7 +170,7 @@ def act(diff, jenkins_map):
170
170
  elif action == "unassign_role_from_user":
171
171
  jenkins_map[instance].unassign_role_from_user(role, user)
172
172
  else:
173
- raise Exception("invalid action: {}".format(action))
173
+ raise Exception(f"invalid action: {action}")
174
174
 
175
175
 
176
176
  def run(dry_run):