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
@@ -5,10 +5,7 @@ from collections.abc import (
5
5
  Iterable,
6
6
  Mapping,
7
7
  )
8
- from typing import (
9
- Any,
10
- Optional,
11
- )
8
+ from typing import Any
12
9
 
13
10
  from sretoolbox.utils import threaded
14
11
 
@@ -46,13 +43,13 @@ def get_cluster_users(
46
43
  users: list[str] = []
47
44
 
48
45
  # get cluster info for current cluster name from clusters list
49
- cluster_info = next((cl for cl in clusters if cl.name == cluster))
46
+ cluster_info = next(cl for cl in clusters if cl.name == cluster)
50
47
 
51
48
  # backwarts compatibiltiy for clusters w/o auth
52
49
  identity_prefixes = ["github"]
53
50
 
54
51
  for auth in cluster_info.auth:
55
- if isinstance(auth, (ClusterAuthOIDCV1, ClusterAuthRHIDPV1)):
52
+ if isinstance(auth, ClusterAuthOIDCV1 | ClusterAuthRHIDPV1):
56
53
  identity_prefixes.append(auth.name)
57
54
 
58
55
  for u in oc.get_users():
@@ -72,7 +69,7 @@ def get_cluster_users(
72
69
 
73
70
  def fetch_current_state(
74
71
  thread_pool_size: int,
75
- internal: Optional[bool],
72
+ internal: bool | None,
76
73
  use_jump_host: bool,
77
74
  ) -> tuple[OCMap, list[Any]]:
78
75
  vault_settings = get_app_interface_vault_settings()
@@ -99,7 +96,7 @@ def fetch_current_state(
99
96
 
100
97
 
101
98
  def fetch_desired_state(
102
- oc_map: Optional[OCMap], enforced_user_keys: Any = None
99
+ oc_map: OCMap | None, enforced_user_keys: Any = None
103
100
  ) -> list[Any]:
104
101
  desired_state = []
105
102
 
@@ -163,16 +160,16 @@ def act(diff: Mapping[str, Any], oc_map: OCMap) -> None:
163
160
  raise Exception("No proper Openshift Client for del_user operation")
164
161
  oc.delete_user(user)
165
162
  else:
166
- raise Exception("invalid action: {}".format(action))
163
+ raise Exception(f"invalid action: {action}")
167
164
 
168
165
 
169
166
  @defer
170
167
  def run(
171
168
  dry_run: bool,
172
169
  thread_pool_size: int = 10,
173
- internal: Optional[bool] = None,
170
+ internal: bool | None = None,
174
171
  use_jump_host: bool = True,
175
- defer: Optional[Callable] = None,
172
+ defer: Callable | None = None,
176
173
  ) -> None:
177
174
  oc_map, current_state = fetch_current_state(
178
175
  thread_pool_size, internal, use_jump_host
reconcile/oum/base.py CHANGED
@@ -4,7 +4,6 @@ from abc import (
4
4
  abstractmethod,
5
5
  )
6
6
  from collections import defaultdict
7
- from typing import Optional
8
7
 
9
8
  from reconcile.gql_definitions.common.ocm_environments import (
10
9
  query as ocm_environment_query,
@@ -56,8 +55,8 @@ from reconcile.utils.runtime.integration import (
56
55
 
57
56
 
58
57
  class OCMUserManagementIntegrationParams(PydanticRunParams):
59
- ocm_environment: Optional[str] = None
60
- ocm_organization_ids: Optional[set[str]] = None
58
+ ocm_environment: str | None = None
59
+ ocm_organization_ids: set[str] | None = None
61
60
  group_provider_specs: list[str]
62
61
 
63
62
 
@@ -88,7 +87,7 @@ class OCMUserManagementIntegration(
88
87
 
89
88
  @abstractmethod
90
89
  def get_user_mgmt_config_for_ocm_env(
91
- self, ocm_env: OCMEnvironment, org_ids: Optional[set[str]]
90
+ self, ocm_env: OCMEnvironment, org_ids: set[str] | None
92
91
  ) -> dict[str, OrganizationUserManagementConfiguration]:
93
92
  """
94
93
  Discover cluster user mgmt configurations in the given OCM environment.
reconcile/oum/labelset.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from collections import defaultdict
2
- from typing import Optional
3
2
 
4
3
  from pydantic import BaseModel
5
4
 
@@ -23,7 +22,7 @@ class _GroupMappingLabelset(BaseModel):
23
22
  the sre-capabilities.user-mgmt.$provider prefix.
24
23
  """
25
24
 
26
- authz_roles: Optional[dict[str, CSV]] = sre_capability_labels.labelset_groupfield(
25
+ authz_roles: dict[str, CSV] | None = sre_capability_labels.labelset_groupfield(
27
26
  group_prefix="authz."
28
27
  )
29
28
 
reconcile/oum/metrics.py CHANGED
@@ -1,4 +1,4 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
@@ -56,7 +56,7 @@ class OCMUserManagementOrganizationActionCounter(
56
56
  ):
57
57
  "Counter for the number of actions taken for an OCM organization"
58
58
 
59
- class Action(str, Enum):
59
+ class Action(StrEnum):
60
60
  AddUser = "add-user"
61
61
  RemoveUser = "remove-user"
62
62
 
reconcile/oum/models.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from collections import defaultdict
2
- from typing import Optional
3
2
 
4
3
  from pydantic import (
5
4
  BaseModel,
@@ -64,7 +63,7 @@ class ClusterRoleReconcileResult(BaseModel):
64
63
 
65
64
  users_added: int = 0
66
65
  users_removed: int = 0
67
- error: Optional[Exception] = None
66
+ error: Exception | None = None
68
67
 
69
68
  class Config:
70
69
  arbitrary_types_allowed = True
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
  from collections import defaultdict
3
3
  from datetime import timedelta
4
- from typing import Optional
5
4
 
6
5
  from reconcile.gql_definitions.fragments.ocm_environment import OCMEnvironment
7
6
  from reconcile.oum.base import OCMUserManagementIntegration
@@ -36,7 +35,7 @@ class OCMStandaloneUserManagementIntegration(OCMUserManagementIntegration):
36
35
  return "ocm-standalone-user-management"
37
36
 
38
37
  def get_user_mgmt_config_for_ocm_env(
39
- self, ocm_env: OCMEnvironment, org_ids: Optional[set[str]]
38
+ self, ocm_env: OCMEnvironment, org_ids: set[str] | None
40
39
  ) -> dict[str, OrganizationUserManagementConfiguration]:
41
40
  ocm_api = init_ocm_base_client(ocm_env, self.secret_reader)
42
41
  clusters_by_org = discover_clusters(
@@ -139,7 +138,7 @@ def user_mgmt_label_key(config_atom: str) -> str:
139
138
 
140
139
  def discover_clusters(
141
140
  ocm_api: OCMBaseClient,
142
- org_ids: Optional[set[str]] = None,
141
+ org_ids: set[str] | None = None,
143
142
  ) -> dict[str, list[ClusterDetails]]:
144
143
  """
145
144
  Discover all clusters with user management enabled on their subscription
@@ -6,10 +6,7 @@ from collections.abc import (
6
6
  Iterable,
7
7
  Mapping,
8
8
  )
9
- from typing import (
10
- Any,
11
- Optional,
12
- )
9
+ from typing import Any
13
10
 
14
11
  import yaml
15
12
  from deepdiff import DeepHash
@@ -58,8 +55,8 @@ class Test(BaseModel):
58
55
  rule_path: str
59
56
  rule: dict
60
57
  rule_length: int
61
- tests: Optional[list[TestContent]]
62
- result: Optional[CommandExecutionResult] = None
58
+ tests: list[TestContent] | None
59
+ result: CommandExecutionResult | None = None
63
60
 
64
61
 
65
62
  class RuleToFetch(BaseModel):
@@ -122,7 +119,7 @@ def fetch_rule_and_tests(
122
119
  def get_rules_and_tests(
123
120
  vault_settings: AppInterfaceSettingsV1,
124
121
  thread_pool_size: int,
125
- cluster_names: Optional[Iterable[str]] = None,
122
+ cluster_names: Iterable[str] | None = None,
126
123
  ) -> list[Test]:
127
124
  """Iterates through all namespaces and returns a list of tests to run"""
128
125
  namespace_with_prom_rules, _ = orb.get_namespaces(
@@ -225,7 +222,7 @@ def check_rules_and_tests(
225
222
  vault_settings: AppInterfaceSettingsV1,
226
223
  alerting_services: Iterable[str],
227
224
  thread_pool_size: int,
228
- cluster_names: Optional[Iterable[str]] = None,
225
+ cluster_names: Iterable[str] | None = None,
229
226
  ) -> list[Test]:
230
227
  """Fetch rules and associated tests, run checks on rules and tests if they exist
231
228
  and return a list of failed checks/tests"""
@@ -247,7 +244,7 @@ def check_rules_and_tests(
247
244
 
248
245
 
249
246
  def run(
250
- dry_run: bool, thread_pool_size: int, cluster_names: Optional[Iterable[str]] = None
247
+ dry_run: bool, thread_pool_size: int, cluster_names: Iterable[str] | None = None
251
248
  ) -> None:
252
249
  """Check prometheus rules syntax and run the tests associated to them"""
253
250
  orb.QONTRACT_INTEGRATION = QONTRACT_INTEGRATION
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
  import sys
3
3
  from collections.abc import Sequence
4
- from typing import Union
5
4
 
6
5
  from reconcile.gql_definitions.quay_membership import quay_membership
7
6
  from reconcile.gql_definitions.quay_membership.quay_membership import (
@@ -81,7 +80,7 @@ def fetch_current_state(quay_api_store):
81
80
  return state
82
81
 
83
82
 
84
- def get_usernames(users: Sequence[Union[UserV1, BotV1]]) -> list[str]:
83
+ def get_usernames(users: Sequence[UserV1 | BotV1]) -> list[str]:
85
84
  return [u.quay_username for u in users if u.quay_username]
86
85
 
87
86
 
reconcile/quay_mirror.py CHANGED
@@ -11,7 +11,6 @@ from collections import (
11
11
  from collections.abc import Iterable
12
12
  from typing import (
13
13
  Any,
14
- Optional,
15
14
  Self,
16
15
  )
17
16
 
@@ -67,11 +66,11 @@ class QuayMirror:
67
66
  def __init__(
68
67
  self,
69
68
  dry_run: bool = False,
70
- control_file_dir: Optional[str] = None,
71
- compare_tags: Optional[bool] = None,
69
+ control_file_dir: str | None = None,
70
+ compare_tags: bool | None = None,
72
71
  compare_tags_interval: int = 86400,
73
- repository_urls: Optional[Iterable[str]] = None,
74
- exclude_repository_urls: Optional[Iterable[str]] = None,
72
+ repository_urls: Iterable[str] | None = None,
73
+ exclude_repository_urls: Iterable[str] | None = None,
75
74
  ) -> None:
76
75
  self.dry_run = dry_run
77
76
  self.gqlapi = gql.get_api()
@@ -113,7 +112,7 @@ class QuayMirror:
113
112
  tempfile.gettempdir(), CONTROL_FILE_NAME
114
113
  )
115
114
 
116
- self._has_enough_time_passed_since_last_compare_tags: Optional[bool] = None
115
+ self._has_enough_time_passed_since_last_compare_tags: bool | None = None
117
116
  self.session = requests.Session()
118
117
 
119
118
  def __enter__(self) -> Self:
@@ -142,8 +141,8 @@ class QuayMirror:
142
141
  @classmethod
143
142
  def process_repos_query(
144
143
  cls,
145
- repository_urls: Optional[Iterable[str]] = None,
146
- exclude_repository_urls: Optional[Iterable[str]] = None,
144
+ repository_urls: Iterable[str] | None = None,
145
+ exclude_repository_urls: Iterable[str] | None = None,
147
146
  session: requests.Session | None = None,
148
147
  timeout: int = REQUEST_TIMEOUT,
149
148
  ) -> defaultdict[OrgKey, list[dict[str, Any]]]:
@@ -370,7 +369,7 @@ class QuayMirror:
370
369
  @staticmethod
371
370
  def check_compare_tags_elapsed_time(path, interval) -> bool:
372
371
  try:
373
- with open(path, "r", encoding="locale") as file_obj:
372
+ with open(path, encoding="locale") as file_obj:
374
373
  last_compare_tags = float(file_obj.read())
375
374
  except FileNotFoundError:
376
375
  return True
@@ -406,11 +405,11 @@ class QuayMirror:
406
405
 
407
406
  def run(
408
407
  dry_run,
409
- control_file_dir: Optional[str],
410
- compare_tags: Optional[bool],
408
+ control_file_dir: str | None,
409
+ compare_tags: bool | None,
411
410
  compare_tags_interval: int,
412
- repository_urls: Optional[Iterable[str]],
413
- exclude_repository_urls: Optional[Iterable[str]],
411
+ repository_urls: Iterable[str] | None,
412
+ exclude_repository_urls: Iterable[str] | None,
414
413
  ):
415
414
  with QuayMirror(
416
415
  dry_run,
@@ -3,7 +3,7 @@ import os
3
3
  import tempfile
4
4
  from collections import defaultdict
5
5
  from collections.abc import Iterable
6
- from typing import Any, Optional, Self
6
+ from typing import Any, Self
7
7
 
8
8
  import requests
9
9
  from sretoolbox.container import (
@@ -30,11 +30,11 @@ class QuayMirrorOrg:
30
30
  def __init__(
31
31
  self,
32
32
  dry_run: bool = False,
33
- control_file_dir: Optional[str] = None,
34
- compare_tags: Optional[bool] = None,
33
+ control_file_dir: str | None = None,
34
+ compare_tags: bool | None = None,
35
35
  compare_tags_interval: int = 28800, # 8 hours
36
- orgs: Optional[Iterable[str]] = None,
37
- repositories: Optional[Iterable[str]] = None,
36
+ orgs: Iterable[str] | None = None,
37
+ repositories: Iterable[str] | None = None,
38
38
  ) -> None:
39
39
  self.dry_run = dry_run
40
40
  self.skopeo_cli = Skopeo(dry_run)
@@ -56,7 +56,7 @@ class QuayMirrorOrg:
56
56
  tempfile.gettempdir(), CONTROL_FILE_NAME
57
57
  )
58
58
 
59
- self._has_enough_time_passed_since_last_compare_tags: Optional[bool] = None
59
+ self._has_enough_time_passed_since_last_compare_tags: bool | None = None
60
60
  self.session = requests.Session()
61
61
 
62
62
  def __enter__(self) -> Self:
@@ -290,11 +290,11 @@ class QuayMirrorOrg:
290
290
 
291
291
  def run(
292
292
  dry_run,
293
- control_file_dir: Optional[str],
294
- compare_tags: Optional[bool],
293
+ control_file_dir: str | None,
294
+ compare_tags: bool | None,
295
295
  compare_tags_interval: int,
296
- orgs: Optional[Iterable[str]],
297
- repositories: Optional[Iterable[str]],
296
+ orgs: Iterable[str] | None,
297
+ repositories: Iterable[str] | None,
298
298
  ):
299
299
  with QuayMirrorOrg(
300
300
  dry_run,
reconcile/queries.py CHANGED
@@ -5,10 +5,7 @@ import shlex
5
5
  from collections.abc import Mapping
6
6
  from dataclasses import dataclass
7
7
  from textwrap import indent
8
- from typing import (
9
- Any,
10
- Optional,
11
- )
8
+ from typing import Any
12
9
 
13
10
  from jinja2 import Template
14
11
 
@@ -25,7 +22,7 @@ SECRET_READER_SETTINGS = """
25
22
  """
26
23
 
27
24
 
28
- def get_secret_reader_settings() -> Optional[Mapping[str, Any]]:
25
+ def get_secret_reader_settings() -> Mapping[str, Any] | None:
29
26
  """Returns SecretReader settings"""
30
27
  gqlapi = gql.get_api()
31
28
  settings = gqlapi.query(SECRET_READER_SETTINGS)["settings"]
@@ -598,7 +595,7 @@ def get_queue_aws_accounts():
598
595
  return get_aws_accounts(uid=uid)
599
596
 
600
597
 
601
- def get_jumphosts(hostname: Optional[str] = None) -> JumphostsQueryData:
598
+ def get_jumphosts(hostname: str | None = None) -> JumphostsQueryData:
602
599
  """Returns all jumphosts"""
603
600
  variables = {}
604
601
  # The dictionary must be empty if no hostname is set.
@@ -2289,7 +2286,7 @@ JIRA_BOARDS_QUERY = """
2289
2286
  """
2290
2287
 
2291
2288
 
2292
- def get_jira_boards(with_slack: Optional[bool] = True):
2289
+ def get_jira_boards(with_slack: bool | None = True):
2293
2290
  """Returns Jira boards resources defined in app-interface"""
2294
2291
  gqlapi = gql.get_api()
2295
2292
  query = Template(JIRA_BOARDS_QUERY).render(with_slack=with_slack)
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
  import sys
3
3
  from typing import (
4
- Optional,
5
4
  cast,
6
5
  )
7
6
 
@@ -23,9 +22,9 @@ QONTRACT_INTEGRATION = "resource-scraper"
23
22
 
24
23
  def run(
25
24
  dry_run: bool,
26
- namespace_name: Optional[str],
27
- resource_kind: Optional[str],
28
- vault_output_path: Optional[str],
25
+ namespace_name: str | None,
26
+ resource_kind: str | None,
27
+ vault_output_path: str | None,
29
28
  ) -> None:
30
29
  """Get resources from clusters and store in Vault."""
31
30
  if not namespace_name:
reconcile/rhidp/common.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import (
3
3
  Iterable,
4
4
  MutableMapping,
5
5
  )
6
- from enum import Enum
6
+ from enum import StrEnum
7
7
  from typing import Any
8
8
  from urllib.parse import urlparse
9
9
 
@@ -44,7 +44,7 @@ ISSUER_LABEL_KEY = sre_capability_label_key("rhidp", "issuer")
44
44
  AUTH_NAME_LABEL_KEY = sre_capability_label_key("rhidp", "name")
45
45
 
46
46
 
47
- class StatusValue(str, Enum):
47
+ class StatusValue(StrEnum):
48
48
  # rhidp and oidc are enabled
49
49
  ENABLED = "enabled"
50
50
  # rhidp and oidc are disabled
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Callable
2
- from typing import Optional
3
2
 
4
3
  from sretoolbox.utils import threaded
5
4
 
@@ -95,8 +94,8 @@ class SaasAutoPromotionsManager:
95
94
 
96
95
  def init_external_dependencies(
97
96
  dry_run: bool,
98
- env_name: Optional[str] = None,
99
- app_name: Optional[str] = None,
97
+ env_name: str | None = None,
98
+ app_name: str | None = None,
100
99
  ) -> tuple[
101
100
  PromotionState,
102
101
  VCS,
@@ -169,9 +168,9 @@ def init_external_dependencies(
169
168
  def run(
170
169
  dry_run: bool,
171
170
  thread_pool_size: int,
172
- env_name: Optional[str] = None,
173
- app_name: Optional[str] = None,
174
- defer: Optional[Callable] = None,
171
+ env_name: str | None = None,
172
+ app_name: str | None = None,
173
+ defer: Callable | None = None,
175
174
  ) -> None:
176
175
  (
177
176
  deployment_state,
@@ -1,7 +1,6 @@
1
1
  from collections.abc import Iterable
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import Optional
5
4
 
6
5
  from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
7
6
  OpenBatcherMergeRequest,
@@ -145,7 +144,7 @@ class Batcher:
145
144
  if not unsubmitted_promotions:
146
145
  return
147
146
 
148
- batch_with_capacity: Optional[OpenBatcherMergeRequest] = None
147
+ batch_with_capacity: OpenBatcherMergeRequest | None = None
149
148
  for mr in self._open_mrs:
150
149
  if mr.is_batchable and len(mr.content_hashes) < batch_limit:
151
150
  batch_with_capacity = mr
@@ -1,6 +1,5 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime
3
- from typing import Optional
4
3
 
5
4
  from reconcile.utils.promotion_state import PromotionState
6
5
  from reconcile.utils.secret_reader import HasSecret
@@ -18,7 +17,7 @@ class DeploymentInfo:
18
17
  success: bool
19
18
  saas_file: str
20
19
  target_config_hash: str
21
- check_in: Optional[datetime]
20
+ check_in: datetime | None
22
21
 
23
22
 
24
23
  class Publisher:
@@ -37,10 +36,10 @@ class Publisher:
37
36
  app_name: str,
38
37
  namespace_name: str,
39
38
  resource_template_name: str,
40
- target_name: Optional[str],
39
+ target_name: str | None,
41
40
  cluster_name: str,
42
- auth_code: Optional[HasSecret],
43
- publish_job_logs: Optional[bool],
41
+ auth_code: HasSecret | None,
42
+ publish_job_logs: bool | None,
44
43
  has_subscriber: bool = True,
45
44
  ):
46
45
  self._ref = ref
@@ -48,7 +47,7 @@ class Publisher:
48
47
  self._auth_code = auth_code
49
48
  self.channels: set[str] = set()
50
49
  self.commit_sha: str = ""
51
- self.deployment_info_by_channel: dict[str, Optional[DeploymentInfo]] = {}
50
+ self.deployment_info_by_channel: dict[str, DeploymentInfo | None] = {}
52
51
  self.uid = uid
53
52
  self.saas_name = saas_name
54
53
  self.saas_file_path = saas_file_path
@@ -2,8 +2,7 @@ import hashlib
2
2
  import logging
3
3
  from collections.abc import Iterable
4
4
  from dataclasses import dataclass
5
- from datetime import datetime, timedelta, timezone
6
- from typing import Optional
5
+ from datetime import UTC, datetime, timedelta
7
6
 
8
7
  from reconcile.gql_definitions.fragments.saas_target_namespace import (
9
8
  SaasTargetNamespace,
@@ -45,6 +44,7 @@ class Subscriber:
45
44
  use_target_config_hash: bool,
46
45
  uid: str,
47
46
  soak_days: int,
47
+ blocked_versions: set[str],
48
48
  ):
49
49
  self.saas_name = saas_name
50
50
  self.template_name = template_name
@@ -59,6 +59,7 @@ class Subscriber:
59
59
  self.soak_days = soak_days
60
60
  self._content_hash = ""
61
61
  self._use_target_config_hash = use_target_config_hash
62
+ self._blocked_versions = blocked_versions
62
63
 
63
64
  def has_diff(self) -> bool:
64
65
  current_hashes = {
@@ -80,7 +81,7 @@ class Subscriber:
80
81
 
81
82
  def _validate_deployment(
82
83
  self, publisher: Publisher, channel: Channel
83
- ) -> Optional[DeploymentInfo]:
84
+ ) -> DeploymentInfo | None:
84
85
  deployment_info = publisher.deployment_info_by_channel.get(channel.name)
85
86
  if not deployment_info:
86
87
  logging.info(
@@ -103,7 +104,7 @@ class Subscriber:
103
104
  We accumulate the time a ref is running on all publishers for this subscriber.
104
105
  We compare that accumulated time with the soak_days setting of the subscriber.
105
106
  """
106
- now = datetime.now(timezone.utc)
107
+ now = datetime.now(UTC)
107
108
  delta = timedelta(days=0)
108
109
  for channel in self.channels:
109
110
  for publisher in channel.publishers:
@@ -142,22 +143,42 @@ class Subscriber:
142
143
  break
143
144
  publisher_refs.add(publisher.commit_sha)
144
145
 
145
- if len(publisher_refs) > 1:
146
+ # By default we keep current state
147
+ self.desired_ref = self.ref
148
+
149
+ if any_bad_deployment:
150
+ logging.info(
151
+ "Subscriber at path %s promotion stopped because of bad publisher deployment",
152
+ self.target_file_path,
153
+ )
154
+ return
155
+
156
+ if len(publisher_refs) != 1:
146
157
  logging.info(
147
158
  "Publishers for subscriber at path %s have mismatching refs: %s",
148
159
  self.target_file_path,
149
160
  publisher_refs,
150
161
  )
151
- if (
152
- len(publisher_refs) != 1
153
- or any_bad_deployment
154
- or not self._passed_accumulated_soak_days()
155
- ):
156
- # We keep current state
157
- self.desired_ref = self.ref
158
- else:
159
- # We have a common single publisher ref w/o any deployment issues
160
- self.desired_ref = next(iter(publisher_refs))
162
+ return
163
+
164
+ if not self._passed_accumulated_soak_days():
165
+ logging.debug(
166
+ "Subscriber at path %s promotion stopped because of soak days",
167
+ self.target_file_path,
168
+ )
169
+ return
170
+
171
+ desired_ref = next(iter(publisher_refs))
172
+ if desired_ref in self._blocked_versions:
173
+ logging.info(
174
+ "Subscriber at path %s promotion stopped because of blocked ref: %s",
175
+ self.target_file_path,
176
+ desired_ref,
177
+ )
178
+ return
179
+
180
+ # Passed all gates -> lets promote desired ref
181
+ self.desired_ref = desired_ref
161
182
 
162
183
  def _compute_desired_config_hashes(self) -> None:
163
184
  """
@@ -86,6 +86,10 @@ class SaasFilesInventory:
86
86
 
87
87
  def _assemble_subscribers_with_auto_promotions(self) -> None:
88
88
  for saas_file in self._saas_files:
89
+ blocked_versions: dict[str, set[str]] = {}
90
+ for code_component in saas_file.app.code_components or []:
91
+ for version in code_component.blocked_versions or []:
92
+ blocked_versions.setdefault(code_component.url, set()).add(version)
89
93
  for resource_template in saas_file.resource_templates:
90
94
  for target in resource_template.targets:
91
95
  file_path = target.path if target.path else saas_file.path
@@ -98,6 +102,7 @@ class SaasFilesInventory:
98
102
  soak_days = (
99
103
  target.promotion.soak_days if target.promotion.soak_days else 0
100
104
  )
105
+ resource_template.url
101
106
  subscriber = Subscriber(
102
107
  uid=target.uid(
103
108
  parent_saas_file_name=saas_file.name,
@@ -109,6 +114,9 @@ class SaasFilesInventory:
109
114
  ref=target.ref,
110
115
  target_namespace=target.namespace,
111
116
  soak_days=soak_days,
117
+ blocked_versions=blocked_versions.get(
118
+ resource_template.url, set()
119
+ ),
112
120
  # Note: this will be refactored at a later point.
113
121
  # https://issues.redhat.com/browse/APPSRE-7516
114
122
  use_target_config_hash=bool(saas_file.publish_job_logs),
@@ -1,9 +1,6 @@
1
1
  import logging
2
2
  import sys
3
- from typing import (
4
- Callable,
5
- Optional,
6
- )
3
+ from collections.abc import Callable
7
4
 
8
5
  from reconcile.jenkins_job_builder import init_jjb
9
6
  from reconcile.status import ExitCodes
@@ -26,7 +23,7 @@ QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
26
23
 
27
24
 
28
25
  @defer
29
- def run(dry_run: bool, defer: Optional[Callable] = None) -> None:
26
+ def run(dry_run: bool, defer: Callable | None = None) -> None:
30
27
  vault_settings = get_app_interface_vault_settings()
31
28
  saasherder_settings = get_saasherder_settings()
32
29
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)