qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__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.

Potentially problematic release.


This version of qontract-reconcile might be problematic. Click here for more details.

Files changed (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -5,8 +5,9 @@ from functools import cached_property
5
5
  from typing import Any, Self
6
6
 
7
7
  from deepdiff import DeepHash
8
- from pydantic import BaseModel
8
+ from pydantic import BaseModel, ConfigDict
9
9
 
10
+ from reconcile.utils.datetime_util import utc_now
10
11
  from reconcile.utils.secret_reader import SecretReaderBase
11
12
  from reconcile.utils.state import State, init_state
12
13
 
@@ -16,7 +17,7 @@ CACHE_SOURCE_DIGEST_METADATA_KEY = "cache-source-digest"
16
17
  LATEST_CACHE_SOURCE_DIGEST_METADATA_KEY = "latest-cache-source-digest"
17
18
 
18
19
 
19
- class CacheKeyWithDigest(BaseModel):
20
+ class CacheKeyWithDigest(BaseModel, frozen=True):
20
21
  integration: str
21
22
  integration_version: str
22
23
  dry_run: bool
@@ -69,9 +70,6 @@ class CacheKeyWithDigest(BaseModel):
69
70
  args.append(f"--shard {self.shard}")
70
71
  return " ".join(args)
71
72
 
72
- class Config:
73
- frozen = True
74
-
75
73
 
76
74
  class CacheKey(BaseModel):
77
75
  integration: str
@@ -121,9 +119,10 @@ class CacheKey(BaseModel):
121
119
  """
122
120
  return self.cache_key_with_digest.build_cli_delete_args()
123
121
 
124
- class Config:
125
- frozen = True
126
- keep_untouched = (cached_property,)
122
+ model_config = ConfigDict(
123
+ frozen=True,
124
+ ignored_types=(cached_property,),
125
+ )
127
126
 
128
127
 
129
128
  class CacheValue(BaseModel):
@@ -167,7 +166,7 @@ class EarlyExitCache:
167
166
 
168
167
  def get(self, key: CacheKey) -> CacheValue:
169
168
  value = self.state.get(str(key))
170
- return CacheValue.parse_obj(value)
169
+ return CacheValue.model_validate(value)
171
170
 
172
171
  def set(
173
172
  self,
@@ -185,7 +184,7 @@ class EarlyExitCache:
185
184
  :param latest_cache_source_digest: latest cache source digest, used to check stale for dry run cache
186
185
  :return: None
187
186
  """
188
- expire_at = datetime.now(tz=UTC) + timedelta(seconds=ttl_seconds)
187
+ expire_at = utc_now() + timedelta(seconds=ttl_seconds)
189
188
  metadata = {
190
189
  EXPIRE_AT_METADATA_KEY: str(int(expire_at.timestamp())),
191
190
  CACHE_SOURCE_DIGEST_METADATA_KEY: key.cache_source_digest,
@@ -193,7 +192,7 @@ class EarlyExitCache:
193
192
  }
194
193
  self.state.add(
195
194
  str(key),
196
- value.dict(),
195
+ value.model_dump(),
197
196
  metadata=metadata,
198
197
  force=True,
199
198
  )
@@ -233,7 +232,7 @@ class EarlyExitCache:
233
232
  int(metadata[EXPIRE_AT_METADATA_KEY]),
234
233
  tz=UTC,
235
234
  )
236
- now = datetime.now(UTC)
235
+ now = utc_now()
237
236
  return now >= expire_at
238
237
 
239
238
  def _head_dry_run_status(
@@ -6,6 +6,8 @@ from typing import (
6
6
  cast,
7
7
  )
8
8
 
9
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
10
+
9
11
  DATE_FORMAT = "%Y-%m-%d"
10
12
 
11
13
 
@@ -17,12 +19,14 @@ DictsOrRoles = TypeVar("DictsOrRoles", bound=Iterable[FilterableRole] | Iterable
17
19
 
18
20
 
19
21
  def date_expired(date: str) -> bool:
20
- exp_date = datetime.datetime.strptime(date, DATE_FORMAT).date()
21
- current_date = datetime.datetime.utcnow().date()
22
+ exp_date = ensure_utc(datetime.datetime.strptime(date, DATE_FORMAT)).date() # noqa: DTZ007
23
+ current_date = utc_now().date()
22
24
  return current_date >= exp_date
23
25
 
24
26
 
25
- def filter(roles: DictsOrRoles | None) -> DictsOrRoles:
27
+ def filter[DictsOrRoles: Iterable[FilterableRole] | Iterable[dict]](
28
+ roles: DictsOrRoles | None,
29
+ ) -> DictsOrRoles:
26
30
  """Filters roles and returns the ones which are not yet expired."""
27
31
  filtered = []
28
32
  for r in roles or []:
@@ -144,13 +144,36 @@ class ExternalResourceSpec:
144
144
  return {}
145
145
 
146
146
  def tags(self, integration: str) -> dict[str, str]:
147
- return {
147
+ tags = {
148
148
  "managed_by_integration": integration,
149
149
  "cluster": self.cluster_name,
150
150
  "namespace": self.namespace_name,
151
151
  "environment": self.namespace["environment"]["name"],
152
152
  "app": self.namespace["app"]["name"],
153
153
  }
154
+ if app_code := self.namespace["app"].get("appCode"):
155
+ tags["app-code"] = app_code
156
+ if cost_center := self.namespace["app"].get("costCenter"):
157
+ tags["cost-center"] = cost_center
158
+ if service_phase := self.namespace["environment"].get("servicePhase"):
159
+ tags["service-phase"] = service_phase
160
+
161
+ resource_tags_str = self.resource.get("tags")
162
+ if resource_tags_str:
163
+ resource_tags = json.loads(resource_tags_str)
164
+ # normalize camelCase keys to kebab-case
165
+ key_mapping = {
166
+ "appCode": "app-code",
167
+ "costCenter": "cost-center",
168
+ "servicePhase": "service-phase",
169
+ }
170
+ normalized_tags = {}
171
+ for key, value in resource_tags.items():
172
+ normalized_key = key_mapping.get(key, key)
173
+ normalized_tags[normalized_key] = value
174
+ tags.update(normalized_tags)
175
+
176
+ return tags
154
177
 
155
178
  def get_secret_field(self, field: str) -> str | None:
156
179
  return self.secret.get(field)
@@ -6,7 +6,7 @@ KeyType = TypeVar("KeyType")
6
6
  ValueType = TypeVar("ValueType")
7
7
 
8
8
 
9
- def remove_none_values_from_dict(
9
+ def remove_none_values_from_dict[KeyType, ValueType](
10
10
  a_dict: dict[KeyType, ValueType | None],
11
11
  ) -> dict[KeyType, ValueType]:
12
12
  """
@@ -263,13 +263,13 @@ class GitLabApi:
263
263
  # we can determine if a pending MR exists based on the title
264
264
  return any(mr.title == title for mr in mrs)
265
265
 
266
- @retry()
266
+ @retry(no_retry_exceptions=(RuntimeError,))
267
267
  def get_project_maintainers(
268
268
  self, repo_url: str | None = None, query: dict | None = None
269
- ) -> list[str] | None:
269
+ ) -> list[str]:
270
270
  project = self.project if repo_url is None else self.get_project(repo_url)
271
271
  if project is None:
272
- return None
272
+ raise RuntimeError("project not found")
273
273
  members = project.members_all.list(iterator=True, query_parameters=query or {})
274
274
  return [m.username for m in members if m.access_level >= 40]
275
275
 
@@ -826,14 +826,16 @@ class GitLabApi:
826
826
  )
827
827
 
828
828
  def get_commit_sha(self, ref: str, repo_url: str) -> str:
829
- project = self.get_project(repo_url)
829
+ if not (project := self.get_project(repo_url)):
830
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
830
831
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
831
832
  return commits[0].id
832
833
 
833
834
  def repository_compare(
834
835
  self, repo_url: str, ref_from: str, ref_to: str
835
836
  ) -> list[dict[str, Any]]:
836
- project = self.get_project(repo_url)
837
+ if not (project := self.get_project(repo_url)):
838
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
837
839
  response: Any = project.repository_compare(ref_from, ref_to)
838
840
  return response.get("commits", [])
839
841
 
@@ -164,7 +164,9 @@ class GlitchtipClient(ApiBase):
164
164
  return ProjectAlert(
165
165
  **self._post(
166
166
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/",
167
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
167
+ data=alert.model_dump(
168
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
169
+ ),
168
170
  )
169
171
  )
170
172
 
@@ -183,7 +185,9 @@ class GlitchtipClient(ApiBase):
183
185
  return ProjectAlert(
184
186
  **self._put(
185
187
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/{alert.pk}/",
186
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
188
+ data=alert.model_dump(
189
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
190
+ ),
187
191
  )
188
192
  )
189
193
 
@@ -3,13 +3,13 @@ from __future__ import annotations
3
3
  import re
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, Self
7
7
 
8
8
  from pydantic import (
9
9
  BaseModel,
10
10
  Field,
11
- root_validator,
12
- validator,
11
+ field_validator,
12
+ model_validator,
13
13
  )
14
14
 
15
15
  if TYPE_CHECKING:
@@ -49,7 +49,8 @@ class Team(BaseModel):
49
49
  slug: str = ""
50
50
  users: list[User] = []
51
51
 
52
- @root_validator(pre=True)
52
+ @model_validator(mode="before")
53
+ @classmethod
53
54
  def name_xor_slug_must_be_set(
54
55
  cls, values: MutableMapping[str, Any]
55
56
  ) -> MutableMapping[str, Any]:
@@ -58,11 +59,11 @@ class Team(BaseModel):
58
59
  ), "name xor slug must be set!"
59
60
  return values
60
61
 
61
- @root_validator
62
- def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
63
- values["slug"] = values.get("slug") or slugify(values.get("name", ""))
64
- values["name"] = slugify(values.get("name", "")) or values.get("slug")
65
- return values
62
+ @model_validator(mode="after")
63
+ def slugify(self) -> Self:
64
+ self.slug = self.slug or slugify(self.name)
65
+ self.name = slugify(self.name) or self.slug
66
+ return self
66
67
 
67
68
  def __lt__(self, other: Team) -> bool:
68
69
  return self.slug < other.slug
@@ -86,16 +87,15 @@ class RecipientType(Enum):
86
87
  WEBHOOK = "webhook"
87
88
 
88
89
 
89
- class ProjectAlertRecipient(BaseModel):
90
+ class ProjectAlertRecipient(
91
+ BaseModel, validate_by_name=True, validate_by_alias=True, use_enum_values=True
92
+ ):
90
93
  pk: int | None = Field(None, alias="id")
91
94
  recipient_type: RecipientType = Field(..., alias="recipientType")
92
95
  url: str = ""
93
96
 
94
- class Config:
95
- allow_population_by_field_name = True
96
- use_enum_values = True
97
-
98
- @validator("recipient_type")
97
+ @field_validator("recipient_type")
98
+ @classmethod
99
99
  def recipient_type_enforce_enum_type(cls, v: str | RecipientType) -> RecipientType:
100
100
  if isinstance(v, RecipientType):
101
101
  return v
@@ -113,17 +113,15 @@ class ProjectAlertRecipient(BaseModel):
113
113
  return hash((self.recipient_type, self.url))
114
114
 
115
115
 
116
- class ProjectAlert(BaseModel):
116
+ class ProjectAlert(BaseModel, validate_by_name=True, validate_by_alias=True):
117
117
  pk: int | None = Field(None, alias="id")
118
118
  name: str
119
119
  timespan_minutes: int = Field(..., alias="timespanMinutes")
120
120
  quantity: int
121
121
  recipients: list[ProjectAlertRecipient] = Field([], alias="alertRecipients")
122
122
 
123
- class Config:
124
- allow_population_by_field_name = True
125
-
126
- @root_validator
123
+ @model_validator(mode="before")
124
+ @classmethod
127
125
  def empty_name(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
128
126
  # name is an empty string if the alert was created manually because it can't be set via UI
129
127
  # use the pk instead.
@@ -141,20 +139,18 @@ class ProjectAlert(BaseModel):
141
139
  )
142
140
 
143
141
 
144
- class Project(BaseModel):
142
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
145
143
  pk: int | None = Field(None, alias="id")
146
144
  name: str
147
145
  slug: str = ""
148
- platform: str | None
146
+ platform: str | None = None
149
147
  teams: list[Team] = []
150
148
  alerts: list[ProjectAlert] = []
151
149
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
152
150
  organization: Organization | None = None
153
151
 
154
- class Config:
155
- allow_population_by_field_name = True
156
-
157
- @root_validator
152
+ @model_validator(mode="before")
153
+ @classmethod
158
154
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
159
155
  values["slug"] = values.get("slug") or slugify(values["name"])
160
156
  return values
@@ -195,7 +191,8 @@ class Organization(BaseModel):
195
191
  teams: list[Team] = []
196
192
  users: list[User] = []
197
193
 
198
- @root_validator
194
+ @model_validator(mode="before")
195
+ @classmethod
199
196
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
200
197
  values["slug"] = values.get("slug") or slugify(values["name"])
201
198
  return values
@@ -212,4 +209,4 @@ class Organization(BaseModel):
212
209
  return hash(self.name)
213
210
 
214
211
 
215
- Project.update_forward_refs()
212
+ Project.model_rebuild()
reconcile/utils/gpg.py CHANGED
@@ -8,7 +8,7 @@ from subprocess import (
8
8
  )
9
9
 
10
10
 
11
- def gpg_encrypt(content, public_gpg_key):
11
+ def gpg_encrypt(content: str, public_gpg_key: str) -> str:
12
12
  public_gpg_key_dec = base64.b64decode(public_gpg_key)
13
13
 
14
14
  with tempfile.TemporaryDirectory() as gnupg_home_dir:
@@ -22,6 +22,8 @@ def gpg_encrypt(content, public_gpg_key):
22
22
  )
23
23
  out = proc.stdout.decode("utf-8")
24
24
  match = re.search(r"<\S+>", out)
25
+ if not match:
26
+ raise ValueError("No recipient found in GPG import output")
25
27
  recipient = match.group(0)[1:-1]
26
28
  # encrypt content
27
29
  proc = run(
@@ -41,5 +43,5 @@ def gpg_encrypt(content, public_gpg_key):
41
43
  stderr=STDOUT,
42
44
  check=True,
43
45
  )
44
- out = proc.stdout
45
- return out.decode("utf-8")
46
+ encrypted_out = proc.stdout
47
+ return encrypted_out.decode("utf-8")
reconcile/utils/gql.py CHANGED
@@ -109,7 +109,7 @@ class GqlApi:
109
109
  if int_name:
110
110
  integrations = self.query(INTEGRATIONS_QUERY, skip_validation=True)
111
111
 
112
- for integration in integrations["integrations"]:
112
+ for integration in integrations["integrations"] if integrations else []:
113
113
  if integration["name"] == int_name:
114
114
  self._valid_schemas = integration["schemas"]
115
115
  break
@@ -142,7 +142,7 @@ class GqlApi:
142
142
  query: str,
143
143
  variables: dict[str, Any] | None = None,
144
144
  skip_validation: bool = False,
145
- ) -> dict[str, Any] | None:
145
+ ) -> dict[str, Any]:
146
146
  try:
147
147
  result = self.client.execute(
148
148
  gql(query), variables, get_execution_result=True
@@ -172,11 +172,8 @@ class GqlApi:
172
172
  if forbidden_schemas:
173
173
  raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
174
174
 
175
- # This is to appease mypy. This exception won't be thrown as this condition
176
- # is already handled above with AssertionError
177
- if result["data"] is None:
178
- raise GqlApiError("`data` not received in GraphQL payload")
179
-
175
+ # make mypy happy
176
+ assert "data" in result and result["data"] is not None
180
177
  return result["data"]
181
178
 
182
179
  def get_template(self, path: str) -> dict[str, str]:
reconcile/utils/helm.py CHANGED
@@ -11,6 +11,7 @@ from typing import Any
11
11
  import yaml
12
12
 
13
13
  from reconcile.utils import git
14
+ from reconcile.utils.json import json_dumps
14
15
  from reconcile.utils.runtime.sharding import ShardSpec
15
16
 
16
17
 
@@ -70,7 +71,7 @@ def do_template(
70
71
  with tempfile.NamedTemporaryFile(
71
72
  mode="w+", encoding="locale"
72
73
  ) as values_file:
73
- values_file.write(json.dumps(values, cls=JSONEncoder))
74
+ values_file.write(json_dumps(values, cls=JSONEncoder))
74
75
  values_file.flush()
75
76
  cmd = [
76
77
  "helm",
@@ -47,7 +47,7 @@ def flatten(
47
47
  Item = TypeVar("Item")
48
48
 
49
49
 
50
- def find_duplicates(items: Iterable[Item]) -> list[Item]:
50
+ def find_duplicates[Item](items: Iterable[Item]) -> list[Item]:
51
51
  return [item for item, count in Counter(items).items() if count > 1]
52
52
 
53
53
 
@@ -21,7 +21,7 @@ class ImapClient:
21
21
  self._server.login(self.user, self.password)
22
22
  return self
23
23
 
24
- def __exit__(self, *args, **kwargs) -> None:
24
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
25
25
  if self._server:
26
26
  self._server.logout()
27
27
 
@@ -40,7 +40,7 @@ class InstrumentedImage(Image):
40
40
 
41
41
 
42
42
  class InstrumentedSkopeo(Skopeo):
43
- def copy(self, *args: Any, **kwargs: Any) -> bytes | str:
43
+ def copy(self, *args: Any, **kwargs: Any) -> None:
44
44
  metrics.copy_count.labels(
45
45
  integration=INTEGRATION_NAME, shard=SHARDS, shard_id=SHARD_ID
46
46
  ).inc()
@@ -140,7 +140,7 @@ class InternalGroupsClient:
140
140
  with self._api as api:
141
141
  return Group(
142
142
  **api.create_group(
143
- data=group.dict(by_alias=True),
143
+ data=group.model_dump(by_alias=True),
144
144
  )
145
145
  )
146
146
 
@@ -155,6 +155,6 @@ class InternalGroupsClient:
155
155
  return Group(
156
156
  **api.update_group(
157
157
  name=group.name,
158
- data=group.dict(by_alias=True),
158
+ data=group.model_dump(by_alias=True),
159
159
  )
160
160
  )
@@ -27,7 +27,7 @@ class Entity(BaseModel):
27
27
  return hash(self.id)
28
28
 
29
29
 
30
- class Group(BaseModel):
30
+ class Group(BaseModel, validate_by_name=True, validate_by_alias=True):
31
31
  name: str
32
32
  description: str
33
33
  member_approval_type: str = Field("self-service", alias="memberApprovalType")
@@ -35,16 +35,18 @@ class Group(BaseModel):
35
35
  owners: list[Entity]
36
36
  display_name: str = Field(..., alias="displayName")
37
37
  notes: str | None = None
38
- rover_group_member_query: str | None = Field(None, alias="roverGroupMemberQuery")
38
+ rover_group_member_query: str | None = Field(
39
+ None, alias="roverGroupMemberQuery", exclude=True
40
+ )
39
41
  rover_group_inclusions: list[Entity] | None = Field(
40
- None, alias="roverGroupInclusions"
42
+ None, alias="roverGroupInclusions", exclude=True
41
43
  )
42
44
  rover_group_exclusions: list[Entity] | None = Field(
43
- None, alias="roverGroupExclusions"
45
+ None, alias="roverGroupExclusions", exclude=True
44
46
  )
45
47
  members: list[Entity] = []
46
- member_of: list[str] | None = Field(None, alias="memberOf")
47
- namespace: str | None = None
48
+ member_of: list[str] | None = Field(None, alias="memberOf", exclude=True)
49
+ namespace: str | None = Field(None, exclude=True)
48
50
 
49
51
  def __eq__(self, other: object) -> bool:
50
52
  if not isinstance(other, Group):
@@ -58,14 +60,3 @@ class Group(BaseModel):
58
60
  and self.notes == other.notes
59
61
  and set(self.members) == set(other.members)
60
62
  )
61
-
62
- class Config:
63
- allow_population_by_field_name = True
64
- # exclude read-only fields in the json/dict dumps
65
- fields = {
66
- "rover_group_member_query": {"exclude": True},
67
- "rover_group_inclusions": {"exclude": True},
68
- "rover_group_exclusions": {"exclude": True},
69
- "member_of": {"exclude": True},
70
- "namespace": {"exclude": True},
71
- }
@@ -231,10 +231,33 @@ class JenkinsApi:
231
231
 
232
232
  return kwargs
233
233
 
234
+ def _is_parameterized_job(self, job_name: str) -> bool:
235
+ api_query = "tree=property[parameterDefinitions[*]]"
236
+ url = f"{self.url}/job/{job_name}/api/json?{api_query}"
237
+
238
+ res = requests.get(
239
+ url,
240
+ verify=self.ssl_verify,
241
+ auth=(self.user, self.password),
242
+ timeout=60,
243
+ )
244
+
245
+ res.raise_for_status()
246
+
247
+ job_info = res.json()
248
+
249
+ return any(
250
+ prop.get("parameterDefinitions") for prop in job_info.get("property", [])
251
+ )
252
+
234
253
  def trigger_job(self, job_name: str) -> None:
235
254
  kwargs = self.get_crumb_kwargs()
236
255
 
237
- url = f"{self.url}/job/{job_name}/build"
256
+ if self._is_parameterized_job(job_name):
257
+ url = f"{self.url}/job/{job_name}/buildWithParameters"
258
+ else:
259
+ url = f"{self.url}/job/{job_name}/build"
260
+
238
261
  res = requests.post(
239
262
  url,
240
263
  verify=self.ssl_verify,
@@ -15,6 +15,7 @@ from reconcile.checkpoint import url_makes_sense
15
15
  from reconcile.github_org import get_default_config
16
16
  from reconcile.utils import gql
17
17
  from reconcile.utils.aws_api import AWSApi
18
+ from reconcile.utils.datetime_util import utc_now
18
19
  from reconcile.utils.github_api import GithubRepositoryApi
19
20
  from reconcile.utils.helpers import flatten
20
21
  from reconcile.utils.jinja2.extensions import B64EncodeExtension, RaiseErrorExtension
@@ -35,6 +36,7 @@ from reconcile.utils.secret_reader import (
35
36
  SecretReader,
36
37
  SecretReaderBase,
37
38
  )
39
+ from reconcile.utils.sloth import generate_sloth_rules
38
40
  from reconcile.utils.vault import SecretFieldNotFoundError
39
41
 
40
42
 
@@ -43,14 +45,11 @@ class Jinja2TemplateError(Exception):
43
45
  super().__init__("error processing jinja2 template: " + str(msg))
44
46
 
45
47
 
46
- class TemplateRenderOptions(BaseModel):
48
+ class TemplateRenderOptions(BaseModel, frozen=True):
47
49
  trim_blocks: bool
48
50
  lstrip_blocks: bool
49
51
  keep_trailing_newline: bool
50
52
 
51
- class Config:
52
- frozen = True
53
-
54
53
  @classmethod
55
54
  def create(
56
55
  cls,
@@ -73,7 +72,7 @@ def compile_jinja2_template(
73
72
  ) -> Any:
74
73
  if not template_render_options:
75
74
  template_render_options = TemplateRenderOptions.create()
76
- env: dict[str, Any] = template_render_options.dict()
75
+ env: dict[str, Any] = template_render_options.model_dump()
77
76
  if extra_curly:
78
77
  env.update({
79
78
  "block_start_string": "{{%",
@@ -258,9 +257,8 @@ def process_jinja2_template(
258
257
  "s3": lookup_s3_object,
259
258
  "s3_ls": list_s3_objects,
260
259
  "flatten_dict": flatten,
261
- "yesterday": lambda: (datetime.datetime.now() - datetime.timedelta(1)).strftime(
262
- "%Y-%m-%d"
263
- ),
260
+ "yesterday": lambda: (utc_now() - datetime.timedelta(1)).strftime("%Y-%m-%d"),
261
+ "sloth_alerts": generate_sloth_rules,
264
262
  })
265
263
  if "_template_mocks" in vars:
266
264
  for k, v in vars["_template_mocks"].items():