qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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 (371) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
  3. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
  4. reconcile/acs_rbac.py +2 -2
  5. reconcile/aus/advanced_upgrade_service.py +18 -12
  6. reconcile/aus/aus_sts_gate_handler.py +59 -0
  7. reconcile/aus/base.py +137 -34
  8. reconcile/aus/cluster_version_data.py +15 -5
  9. reconcile/aus/models.py +3 -1
  10. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  11. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  12. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  13. reconcile/aus/version_gate_approver.py +1 -16
  14. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
  15. reconcile/automated_actions/config/integration.py +16 -4
  16. reconcile/aws_account_manager/integration.py +21 -9
  17. reconcile/aws_account_manager/reconciler.py +3 -3
  18. reconcile/aws_account_manager/utils.py +1 -1
  19. reconcile/aws_ami_cleanup/integration.py +8 -12
  20. reconcile/aws_ami_share.py +69 -62
  21. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  22. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  23. reconcile/aws_iam_keys.py +1 -0
  24. reconcile/aws_saml_idp/integration.py +12 -4
  25. reconcile/aws_saml_roles/integration.py +30 -23
  26. reconcile/aws_version_sync/integration.py +6 -12
  27. reconcile/change_owners/README.md +1 -1
  28. reconcile/change_owners/bundle.py +3 -3
  29. reconcile/change_owners/change_log_tracking.py +3 -2
  30. reconcile/change_owners/change_owners.py +108 -42
  31. reconcile/change_owners/decision.py +1 -1
  32. reconcile/change_owners/diff.py +0 -2
  33. reconcile/checkpoint.py +11 -3
  34. reconcile/cli.py +94 -11
  35. reconcile/dashdotdb_dora.py +5 -12
  36. reconcile/dashdotdb_slo.py +1 -1
  37. reconcile/database_access_manager.py +123 -117
  38. reconcile/dynatrace_token_provider/integration.py +1 -1
  39. reconcile/endpoints_discovery/integration.py +4 -1
  40. reconcile/endpoints_discovery/merge_request.py +1 -1
  41. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  42. reconcile/external_resources/factories.py +4 -6
  43. reconcile/external_resources/integration.py +1 -1
  44. reconcile/external_resources/manager.py +8 -6
  45. reconcile/external_resources/meta.py +0 -1
  46. reconcile/external_resources/metrics.py +1 -1
  47. reconcile/external_resources/model.py +19 -15
  48. reconcile/external_resources/reconciler.py +7 -4
  49. reconcile/external_resources/secrets_sync.py +6 -10
  50. reconcile/external_resources/state.py +26 -16
  51. reconcile/fleet_labeler/integration.py +1 -1
  52. reconcile/gabi_authorized_users.py +5 -2
  53. reconcile/gcp_image_mirror.py +2 -2
  54. reconcile/github_org.py +1 -1
  55. reconcile/github_owners.py +4 -0
  56. reconcile/gitlab_housekeeping.py +13 -15
  57. reconcile/gitlab_members.py +6 -12
  58. reconcile/gitlab_owners.py +15 -11
  59. reconcile/gitlab_permissions.py +8 -12
  60. reconcile/glitchtip_project_alerts/integration.py +3 -1
  61. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  62. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  63. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  64. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  65. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  66. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  67. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  68. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  69. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  70. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
  71. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  72. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  73. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  74. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  75. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  76. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  77. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  78. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  79. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  80. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  81. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  84. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  85. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  87. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  88. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  89. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  90. reconcile/gql_definitions/common/apps.py +5 -5
  91. reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
  92. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  93. reconcile/gql_definitions/common/clusters.py +7 -5
  94. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  95. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  96. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  97. reconcile/gql_definitions/common/github_orgs.py +5 -5
  98. reconcile/gql_definitions/common/jira_settings.py +5 -5
  99. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  100. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  101. reconcile/gql_definitions/common/namespaces.py +5 -5
  102. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  103. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  104. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  105. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  106. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  107. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  108. reconcile/gql_definitions/common/quay_instances.py +5 -5
  109. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  110. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  111. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  112. reconcile/gql_definitions/common/saas_files.py +5 -5
  113. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  114. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  115. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  116. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  117. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  118. reconcile/gql_definitions/common/users.py +5 -5
  119. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  120. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  121. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  122. reconcile/gql_definitions/cost_report/settings.py +5 -5
  123. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  124. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  125. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  126. reconcile/gql_definitions/email_sender/apps.py +5 -5
  127. reconcile/gql_definitions/email_sender/emails.py +5 -5
  128. reconcile/gql_definitions/email_sender/users.py +5 -5
  129. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  130. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  131. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  132. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
  133. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  134. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  135. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  136. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  138. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  139. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  141. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  142. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  143. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  144. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  145. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  146. reconcile/gql_definitions/fragments/disable.py +5 -5
  147. reconcile/gql_definitions/fragments/email_service.py +5 -5
  148. reconcile/gql_definitions/fragments/email_user.py +5 -5
  149. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  150. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  151. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  152. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  153. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  154. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  155. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  156. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  157. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  158. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  159. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  160. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  161. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  162. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  163. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  164. reconcile/gql_definitions/fragments/user.py +5 -5
  165. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  166. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  167. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  168. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  169. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  170. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  171. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  172. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  173. reconcile/gql_definitions/integrations/integrations.py +5 -5
  174. reconcile/gql_definitions/introspection.json +775 -136
  175. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  176. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  177. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  178. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  179. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  180. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  181. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  182. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  183. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  184. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  185. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  186. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  187. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  188. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  189. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  190. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  191. reconcile/gql_definitions/rhcs/certs.py +25 -79
  192. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  193. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  194. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  195. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  196. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  197. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  198. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  199. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  200. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  201. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  202. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  203. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  204. reconcile/gql_definitions/status_board/status_board.py +5 -5
  205. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  206. reconcile/gql_definitions/templating/template_collection.py +5 -5
  207. reconcile/gql_definitions/templating/templates.py +5 -5
  208. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  209. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  210. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  211. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  212. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  213. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  214. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  215. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  216. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  217. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
  218. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  219. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  220. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  221. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  222. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
  223. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
  224. reconcile/integrations_manager.py +3 -3
  225. reconcile/jenkins_worker_fleets.py +10 -8
  226. reconcile/jira_permissions_validator.py +237 -122
  227. reconcile/ldap_groups/integration.py +1 -1
  228. reconcile/ocm/types.py +35 -57
  229. reconcile/ocm_aws_infrastructure_access.py +1 -1
  230. reconcile/ocm_clusters.py +4 -4
  231. reconcile/ocm_labels/integration.py +3 -2
  232. reconcile/ocm_machine_pools.py +33 -27
  233. reconcile/openshift_base.py +113 -4
  234. reconcile/openshift_cluster_bots.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +96 -101
  237. reconcile/openshift_resources_base.py +6 -2
  238. reconcile/openshift_rhcs_certs.py +74 -37
  239. reconcile/openshift_rolebindings.py +7 -11
  240. reconcile/openshift_saas_deploy.py +4 -5
  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 +2 -2
  244. reconcile/openshift_upgrade_watcher.py +4 -4
  245. reconcile/oum/labelset.py +5 -3
  246. reconcile/oum/models.py +1 -4
  247. reconcile/prometheus_rules_tester/integration.py +3 -3
  248. reconcile/quay_base.py +25 -6
  249. reconcile/quay_membership.py +55 -29
  250. reconcile/quay_mirror.py +1 -1
  251. reconcile/quay_mirror_org.py +6 -4
  252. reconcile/quay_permissions.py +81 -75
  253. reconcile/quay_repos.py +35 -37
  254. reconcile/queries.py +132 -1
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +16 -5
  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/skupper_network/integration.py +2 -2
  260. reconcile/slack_usergroups.py +35 -14
  261. reconcile/sql_query.py +1 -0
  262. reconcile/status_board.py +6 -6
  263. reconcile/statuspage/atlassian.py +7 -7
  264. reconcile/statuspage/integrations/maintenances.py +4 -3
  265. reconcile/statuspage/page.py +4 -9
  266. reconcile/statuspage/status.py +5 -8
  267. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  268. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  269. reconcile/templating/lib/rendering.py +3 -3
  270. reconcile/templating/renderer.py +2 -2
  271. reconcile/templating/validator.py +4 -4
  272. reconcile/terraform_aws_route53.py +7 -1
  273. reconcile/terraform_cloudflare_dns.py +3 -3
  274. reconcile/terraform_cloudflare_resources.py +5 -5
  275. reconcile/terraform_cloudflare_users.py +3 -2
  276. reconcile/terraform_init/integration.py +187 -23
  277. reconcile/terraform_repo.py +16 -12
  278. reconcile/terraform_resources.py +6 -6
  279. reconcile/terraform_tgw_attachments.py +27 -19
  280. reconcile/terraform_users.py +7 -0
  281. reconcile/terraform_vpc_peerings.py +14 -3
  282. reconcile/terraform_vpc_resources/integration.py +20 -8
  283. reconcile/terraform_vpc_resources/merge_request.py +12 -2
  284. reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
  285. reconcile/typed_queries/aws_account_tags.py +41 -0
  286. reconcile/typed_queries/cost_report/app_names.py +1 -1
  287. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  288. reconcile/typed_queries/saas_files.py +20 -15
  289. reconcile/typed_queries/status_board.py +2 -2
  290. reconcile/unleash_feature_toggles/integration.py +4 -2
  291. reconcile/utils/acs/base.py +6 -3
  292. reconcile/utils/acs/policies.py +2 -2
  293. reconcile/utils/aws_api.py +51 -20
  294. reconcile/utils/aws_api_typed/api.py +38 -9
  295. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  296. reconcile/utils/aws_api_typed/logs.py +73 -0
  297. reconcile/utils/aws_api_typed/organization.py +4 -2
  298. reconcile/utils/binary.py +7 -12
  299. reconcile/utils/datetime_util.py +67 -0
  300. reconcile/utils/deadmanssnitch_api.py +1 -1
  301. reconcile/utils/differ.py +2 -3
  302. reconcile/utils/early_exit_cache.py +11 -12
  303. reconcile/utils/environ.py +5 -0
  304. reconcile/utils/expiration.py +7 -3
  305. reconcile/utils/external_resource_spec.py +2 -0
  306. reconcile/utils/filtering.py +1 -1
  307. reconcile/utils/gitlab_api.py +19 -5
  308. reconcile/utils/glitchtip/client.py +6 -2
  309. reconcile/utils/glitchtip/models.py +25 -28
  310. reconcile/utils/gql.py +4 -7
  311. reconcile/utils/helpers.py +1 -1
  312. reconcile/utils/instrumented_wrappers.py +1 -1
  313. reconcile/utils/internal_groups/client.py +2 -2
  314. reconcile/utils/internal_groups/models.py +8 -17
  315. reconcile/utils/jinja2/utils.py +6 -101
  316. reconcile/utils/jira_client.py +82 -63
  317. reconcile/utils/jjb_client.py +26 -13
  318. reconcile/utils/jobcontroller/controller.py +2 -2
  319. reconcile/utils/jobcontroller/models.py +17 -1
  320. reconcile/utils/json.py +43 -1
  321. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  322. reconcile/utils/membershipsources/models.py +16 -23
  323. reconcile/utils/membershipsources/resolver.py +4 -2
  324. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  325. reconcile/utils/merge_request_manager/parser.py +6 -6
  326. reconcile/utils/metrics.py +5 -5
  327. reconcile/utils/models.py +304 -82
  328. reconcile/utils/mr/app_interface_reporter.py +2 -2
  329. reconcile/utils/mr/notificator.py +3 -3
  330. reconcile/utils/mr/update_access_report_base.py +3 -4
  331. reconcile/utils/mr/user_maintenance.py +3 -2
  332. reconcile/utils/oc.py +252 -201
  333. reconcile/utils/oc_filters.py +3 -3
  334. reconcile/utils/ocm/addons.py +0 -1
  335. reconcile/utils/ocm/base.py +17 -20
  336. reconcile/utils/ocm/cluster_groups.py +1 -1
  337. reconcile/utils/ocm/identity_providers.py +2 -2
  338. reconcile/utils/ocm/labels.py +1 -1
  339. reconcile/utils/ocm/products.py +8 -8
  340. reconcile/utils/ocm/search_filters.py +3 -6
  341. reconcile/utils/ocm/service_log.py +4 -6
  342. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  343. reconcile/utils/openshift_resource.py +8 -3
  344. reconcile/utils/pagerduty_api.py +10 -7
  345. reconcile/utils/promotion_state.py +6 -11
  346. reconcile/utils/quay_api.py +74 -87
  347. reconcile/utils/raw_github_api.py +1 -1
  348. reconcile/utils/rhcsv2_certs.py +86 -23
  349. reconcile/utils/rosa/session.py +16 -0
  350. reconcile/utils/runtime/integration.py +2 -3
  351. reconcile/utils/runtime/runner.py +2 -2
  352. reconcile/utils/saasherder/interfaces.py +13 -20
  353. reconcile/utils/saasherder/models.py +23 -20
  354. reconcile/utils/saasherder/saasherder.py +50 -27
  355. reconcile/utils/slack_api.py +2 -2
  356. reconcile/utils/sloth.py +171 -2
  357. reconcile/utils/structs.py +1 -1
  358. reconcile/utils/terraform_client.py +5 -4
  359. reconcile/utils/terrascript_aws_client.py +274 -124
  360. reconcile/utils/unleash/server.py +2 -8
  361. reconcile/utils/vault.py +5 -12
  362. reconcile/utils/vcs.py +8 -8
  363. reconcile/vault_replication.py +107 -42
  364. reconcile/vpc_peerings_validator.py +13 -0
  365. tools/app_interface_reporter.py +4 -4
  366. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  367. tools/cli_commands/cost_report/view.py +7 -6
  368. tools/cli_commands/erv2.py +1 -1
  369. tools/qontract_cli.py +28 -17
  370. tools/template_validation.py +3 -1
  371. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
reconcile/utils/vcs.py CHANGED
@@ -140,7 +140,7 @@ class VCS:
140
140
  gitlab_instances: Iterable[GitlabInstanceV1],
141
141
  ) -> GitLabApi:
142
142
  return GitLabApi(
143
- next(iter(gitlab_instances)).dict(by_alias=True),
143
+ next(iter(gitlab_instances)).model_dump(by_alias=True),
144
144
  secret_reader=self._secret_reader,
145
145
  )
146
146
 
@@ -150,7 +150,7 @@ class VCS:
150
150
  app_interface_repo_url: str,
151
151
  ) -> GitLabApi:
152
152
  return GitLabApi(
153
- next(iter(gitlab_instances)).dict(by_alias=True),
153
+ next(iter(gitlab_instances)).model_dump(by_alias=True),
154
154
  secret_reader=self._secret_reader,
155
155
  project_url=app_interface_repo_url,
156
156
  )
@@ -221,26 +221,26 @@ class VCS:
221
221
  match repo_info.platform:
222
222
  case "github":
223
223
  github = self._init_github(repo_url=repo_url, auth_code=auth_code)
224
- data = github.compare(commit_from=commit_from, commit_to=commit_to)
225
224
  return [
226
225
  Commit(
227
226
  repo=repo_url,
228
227
  sha=gh_commit.sha,
229
228
  date=gh_commit.commit.committer.date,
230
229
  )
231
- for gh_commit in data
230
+ for gh_commit in github.compare(
231
+ commit_from=commit_from, commit_to=commit_to
232
+ )
232
233
  ]
233
234
  case "gitlab":
234
- data = self._gitlab_instance.repository_compare(
235
- repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
236
- )
237
235
  return [
238
236
  Commit(
239
237
  repo=repo_url,
240
238
  sha=gl_commit["id"],
241
239
  date=datetime.fromisoformat(gl_commit["committed_date"]),
242
240
  )
243
- for gl_commit in data
241
+ for gl_commit in self._gitlab_instance.repository_compare(
242
+ repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
243
+ )
244
244
  ]
245
245
  case _:
246
246
  raise ValueError(f"Unsupported repository URL: {repo_url}")
@@ -84,6 +84,54 @@ def deep_copy_versions(
84
84
  dest_vault.write(secret=write_dict, decode_base64=False, force=True)
85
85
 
86
86
 
87
+ def _handle_missing_destination_secret(
88
+ dry_run: bool,
89
+ source_vault: VaultClient,
90
+ dest_vault: VaultClient,
91
+ source_data: dict,
92
+ source_version: int | None,
93
+ path: str,
94
+ ) -> None:
95
+ """Handles replication when destination secret is missing or has no accessible versions.
96
+
97
+ This covers two scenarios:
98
+ 1. Secret doesn't exist at all in destination vault (SecretNotFoundError)
99
+ 2. Secret exists but all versions are deleted in KV v2 (SecretVersionNotFoundError)
100
+
101
+ For both cases, we replicate from source starting from version 0 (or copy directly for v1).
102
+
103
+ Args:
104
+ dry_run: Whether this is a dry run
105
+ source_vault: Source vault client (needed for v2 deep copy)
106
+ dest_vault: Destination vault client
107
+ source_data: Already retrieved source secret data
108
+ source_version: Source secret version (None for v1 secrets)
109
+ path: Secret path
110
+ """
111
+ if source_version is None:
112
+ # v1 secret - just copy it over using the already-retrieved source data
113
+ logging.info(["replicate_vault_secret", "Copying v1 secret", path])
114
+ if not dry_run:
115
+ write_dict = {"path": path, "data": source_data}
116
+ dest_vault.write(secret=write_dict, decode_base64=False, force=True)
117
+ else:
118
+ # v2 secret - deep copy all versions starting from 0
119
+ # Note: deep_copy_versions will read individual versions from source as needed
120
+ logging.info([
121
+ "replicate_vault_secret",
122
+ "Deep copying v2 secret versions",
123
+ path,
124
+ ])
125
+ deep_copy_versions(
126
+ dry_run=dry_run,
127
+ source_vault=source_vault,
128
+ dest_vault=dest_vault,
129
+ current_dest_version=0,
130
+ current_source_version=source_version,
131
+ path=path,
132
+ )
133
+
134
+
87
135
  def write_dummy_versions(
88
136
  dry_run: bool,
89
137
  dest_vault: VaultClient,
@@ -133,48 +181,65 @@ def copy_vault_secret(
133
181
 
134
182
  try:
135
183
  dest_data, dest_version = dest_vault.read_all_with_version(secret_dict)
136
- if dest_version is None and version is None:
137
- # v1 secrets don't have version
138
- if source_data == dest_data:
139
- # If the secret is the same in both vaults, we don't need
140
- # to copy it again
141
- return
142
-
143
- secret, _ = source_vault.read_all_with_version(secret_dict)
144
- write_dict = {"path": path, "data": secret}
145
- logging.info(["replicate_vault_secret", path])
146
- if not dry_run:
147
- # Using force=True to write the secret to force the vault client even
148
- # if the data is the same as the previous version. This happens in
149
- # some secrets even tho the library does not create it
150
- dest_vault.write(secret=write_dict, decode_base64=False, force=True)
151
- elif dest_version < version:
152
- deep_copy_versions(
153
- dry_run=dry_run,
154
- source_vault=source_vault,
155
- dest_vault=dest_vault,
156
- current_dest_version=dest_version,
157
- current_source_version=version,
158
- path=path,
159
- )
160
- except (SecretVersionNotFoundError, SecretNotFoundError):
161
- logging.info(["replicate_vault_secret", "Secret not found", path])
162
- # Handle v1 secrets where version is None and we don't need to deep sync.
163
- if version is None:
164
- logging.info(["replicate_vault_secret", path])
165
- if not dry_run:
166
- secret, _ = source_vault.read_all_with_version(secret_dict)
167
- write_dict = {"path": path, "data": secret}
168
- dest_vault.write(secret=write_dict, decode_base64=False, force=True)
169
- else:
170
- deep_copy_versions(
171
- dry_run=dry_run,
172
- source_vault=source_vault,
173
- dest_vault=dest_vault,
174
- current_dest_version=0,
175
- current_source_version=version,
176
- path=path,
177
- )
184
+ except SecretVersionNotFoundError:
185
+ # Handle KV v2 case where secret metadata exists but latest version is deleted
186
+ # This occurs when someone manually deletes the latest version but the secret
187
+ # metadata still exists in Vault. This should only happen for v2 secrets.
188
+ logging.info([
189
+ "replicate_vault_secret",
190
+ "KV v2 latest version deleted, replicating all versions",
191
+ path,
192
+ ])
193
+ _handle_missing_destination_secret(
194
+ dry_run=dry_run,
195
+ source_vault=source_vault,
196
+ dest_vault=dest_vault,
197
+ source_data=source_data,
198
+ source_version=version,
199
+ path=path,
200
+ )
201
+ return
202
+ except SecretNotFoundError:
203
+ # Handle case where secret doesn't exist at all in destination vault
204
+ logging.info([
205
+ "replicate_vault_secret",
206
+ "Secret not found in destination",
207
+ path,
208
+ ])
209
+ _handle_missing_destination_secret(
210
+ dry_run=dry_run,
211
+ source_vault=source_vault,
212
+ dest_vault=dest_vault,
213
+ source_data=source_data,
214
+ source_version=version,
215
+ path=path,
216
+ )
217
+ return
218
+
219
+ # If we reach here, we successfully read the destination secret
220
+ if dest_version is None or version is None:
221
+ # v1 secrets don't have version
222
+ if source_data == dest_data:
223
+ # If the secret is the same in both vaults, we don't need
224
+ # to copy it again
225
+ return
226
+
227
+ write_dict = {"path": path, "data": source_data}
228
+ logging.info(["replicate_vault_secret", path])
229
+ if not dry_run:
230
+ # Using force=True to write the secret to force the vault client even
231
+ # if the data is the same as the previous version. This happens in
232
+ # some secrets even tho the library does not create it
233
+ dest_vault.write(secret=write_dict, decode_base64=False, force=True)
234
+ elif dest_version < version:
235
+ deep_copy_versions(
236
+ dry_run=dry_run,
237
+ source_vault=source_vault,
238
+ dest_vault=dest_vault,
239
+ current_dest_version=dest_version,
240
+ current_source_version=version,
241
+ path=path,
242
+ )
178
243
 
179
244
 
180
245
  def check_invalid_paths(
@@ -159,6 +159,19 @@ def validate_no_public_to_public_peerings(
159
159
  if peer.internal or (peer.spec and peer.spec.private):
160
160
  continue
161
161
 
162
+ # If both sides are allowed to override this check, then we can
163
+ # allow the peering.
164
+ if (
165
+ cluster.allowed_to_bypass_public_peering_restriction
166
+ and peer.allowed_to_bypass_public_peering_restriction
167
+ ):
168
+ logging.debug(
169
+ f"{cluster.name} and {peer.name} are both allowed to skip \
170
+ the check 'no peering with public clusters' check, so their \
171
+ peering is allowed"
172
+ )
173
+ continue
174
+
162
175
  valid = False
163
176
  pair = {cluster.name, peer.name}
164
177
  if pair in found_pairs:
@@ -4,7 +4,6 @@ import os
4
4
  import textwrap
5
5
  from collections.abc import Mapping, MutableMapping
6
6
  from datetime import (
7
- UTC,
8
7
  datetime,
9
8
  )
10
9
 
@@ -29,6 +28,7 @@ from reconcile.cli import (
29
28
  )
30
29
  from reconcile.jenkins_job_builder import init_jjb
31
30
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
31
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
32
32
  from reconcile.utils.mr import CreateAppInterfaceReporter
33
33
  from reconcile.utils.runtime.environment import init_env
34
34
  from reconcile.utils.secret_reader import SecretReader
@@ -189,8 +189,8 @@ def get_apps_data(
189
189
  apps = queries.get_apps()
190
190
  jjb = init_jjb(secret_reader)
191
191
  jenkins_map = jenkins_base.get_jenkins_map()
192
- time_limit = date - relativedelta(months=month_delta)
193
- timestamp_limit = int(time_limit.replace(tzinfo=UTC).timestamp())
192
+ time_limit = ensure_utc(date) - relativedelta(months=month_delta)
193
+ timestamp_limit = int(time_limit.timestamp())
194
194
 
195
195
  secret_content = secret_reader.read_all({"path": DASHDOTDB_SECRET})
196
196
  dashdotdb_url = secret_content["url"]
@@ -411,7 +411,7 @@ def main(
411
411
  ) -> None:
412
412
  init_env(log_level=log_level, config_file=configfile)
413
413
 
414
- now = datetime.now()
414
+ now = utc_now()
415
415
  apps = get_apps_data(now, thread_pool_size=thread_pool_size)
416
416
 
417
417
  reports = [Report(app, now).to_message() for app in apps]
@@ -74,7 +74,7 @@ class CostManagementApi(ApiBase):
74
74
  timeout=self.read_timeout,
75
75
  )
76
76
  response.raise_for_status()
77
- return AwsReportCostResponse.parse_obj(response.json())
77
+ return AwsReportCostResponse.model_validate(response.json())
78
78
 
79
79
  def get_openshift_costs_report(
80
80
  self,
@@ -97,7 +97,7 @@ class CostManagementApi(ApiBase):
97
97
  timeout=self.read_timeout,
98
98
  )
99
99
  response.raise_for_status()
100
- return OpenShiftReportCostResponse.parse_obj(response.json())
100
+ return OpenShiftReportCostResponse.model_validate(response.json())
101
101
 
102
102
  def get_openshift_cost_optimization_report(
103
103
  self,
@@ -120,7 +120,7 @@ class CostManagementApi(ApiBase):
120
120
  response.raise_for_status()
121
121
 
122
122
  data = self._get_paginated(response)
123
- return OpenShiftCostOptimizationReportResponse.parse_obj(data)
123
+ return OpenShiftCostOptimizationReportResponse.model_validate(data)
124
124
 
125
125
  def _get_paginated(
126
126
  self,
@@ -4,6 +4,7 @@ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
+ from reconcile.utils.json import json_dumps
7
8
  from tools.cli_commands.cost_report.model import OptimizationReport, Report
8
9
 
9
10
  LAYOUT = """\
@@ -244,7 +245,7 @@ def render_summary(
244
245
  return template.format(
245
246
  date=get_date(reports),
246
247
  total_cost=format_cost_value(total_cost),
247
- json_table=json_table.json(indent=2),
248
+ json_table=json_dumps(json_table, indent=2, mode="python"),
248
249
  )
249
250
 
250
251
 
@@ -274,7 +275,7 @@ def render_month_over_month_change(reports: Mapping[str, Report]) -> str:
274
275
  )
275
276
  return MONTH_OVER_MONTH_CHANGE.format(
276
277
  date=get_date(reports),
277
- json_table=json_table.json(indent=2),
278
+ json_table=json_dumps(json_table, indent=2, mode="python"),
278
279
  )
279
280
 
280
281
 
@@ -304,7 +305,7 @@ def render_aws_services_cost(
304
305
  items_total=format_cost_value(report.items_total),
305
306
  items_delta_value=format_delta_value(report.items_delta_value),
306
307
  items_delta_percent=format_delta_percent(report.items_delta_percent),
307
- json_table=json_table.json(indent=2),
308
+ json_table=json_dumps(json_table, indent=2, mode="python"),
308
309
  )
309
310
 
310
311
 
@@ -316,7 +317,7 @@ def render_openshift_workloads_cost(
316
317
  items_total=format_cost_value(report.items_total),
317
318
  items_delta_value=format_delta_value(report.items_delta_value),
318
319
  items_delta_percent=format_delta_percent(report.items_delta_percent),
319
- json_table=json_table.json(indent=2),
320
+ json_table=json_dumps(json_table, indent=2, mode="python"),
320
321
  )
321
322
 
322
323
 
@@ -362,7 +363,7 @@ def render_child_apps_cost(report: Report) -> str:
362
363
  )
363
364
  return CHILD_APPS_COST.format(
364
365
  child_apps_total=format_cost_value(report.child_apps_total),
365
- json_table=json_table.json(indent=2),
366
+ json_table=json_dumps(json_table, indent=2, mode="python"),
366
367
  )
367
368
 
368
369
 
@@ -509,7 +510,7 @@ def render_optimization(
509
510
  )
510
511
  return OPTIMIZATION.format(
511
512
  app_name=report.app_name,
512
- json_table=json_table.json(indent=2),
513
+ json_table=json_dumps(json_table, indent=2, mode="python"),
513
514
  )
514
515
 
515
516
 
@@ -133,7 +133,7 @@ class Erv2Cli:
133
133
 
134
134
  @property
135
135
  def input_data(self) -> str:
136
- return self._resource.json(exclude={"data": {FLAG_RESOURCE_MANAGED_BY_ERV2}})
136
+ return self._resource.export(exclude={"data": {FLAG_RESOURCE_MANAGED_BY_ERV2}})
137
137
 
138
138
  @property
139
139
  def image(self) -> str:
tools/qontract_cli.py CHANGED
@@ -13,7 +13,6 @@ import tempfile
13
13
  import textwrap
14
14
  from collections import defaultdict
15
15
  from datetime import (
16
- UTC,
17
16
  datetime,
18
17
  timedelta,
19
18
  )
@@ -122,6 +121,7 @@ from reconcile.utils.binary import (
122
121
  binary,
123
122
  binary_version,
124
123
  )
124
+ from reconcile.utils.datetime_util import from_utc_iso_format, utc_now
125
125
  from reconcile.utils.early_exit_cache import (
126
126
  CacheKey,
127
127
  CacheKeyWithDigest,
@@ -141,6 +141,7 @@ from reconcile.utils.gitlab_api import (
141
141
  )
142
142
  from reconcile.utils.glitchtip.client import GlitchtipClient
143
143
  from reconcile.utils.gql import GqlApiSingleton
144
+ from reconcile.utils.json import json_dumps
144
145
  from reconcile.utils.keycloak import (
145
146
  KeycloakAPI,
146
147
  SSOClient,
@@ -417,8 +418,8 @@ def get_upgrade_policies_data(
417
418
  upgrade_next_run = None
418
419
  upgrade_emoji = "💫"
419
420
  if upgrade_next_run:
420
- dt = datetime.strptime(upgrade_next_run, "%Y-%m-%dT%H:%M:%SZ")
421
- now = datetime.utcnow()
421
+ dt = from_utc_iso_format(upgrade_next_run)
422
+ now = utc_now()
422
423
  if dt > now:
423
424
  upgrade_emoji = "⏰"
424
425
  hours_ago = (now - dt).total_seconds() / 3600
@@ -841,7 +842,7 @@ def alert_report(
841
842
  )
842
843
  sys.exit(1)
843
844
 
844
- now = datetime.utcnow()
845
+ now = utc_now()
845
846
  from_timestamp = int((now - timedelta(days=days)).timestamp())
846
847
  to_timestamp = int(now.timestamp())
847
848
 
@@ -1566,7 +1567,7 @@ def rosa_create_cluster_command(ctx: click.Context, cluster_name: str) -> None:
1566
1567
  billing_account = account.billing_account.uid
1567
1568
  else:
1568
1569
  with AWSApi(
1569
- 1, [account.dict(by_alias=True)], settings=settings, init_users=False
1570
+ 1, [account.model_dump(by_alias=True)], settings=settings, init_users=False
1570
1571
  ) as aws_api:
1571
1572
  billing_account = aws_api.get_organization_billing_account(account.name)
1572
1573
 
@@ -1750,7 +1751,7 @@ def aws_terraform_resources(ctx: click.Context) -> None:
1750
1751
  for ns_info in namespaces:
1751
1752
  specs = (
1752
1753
  get_external_resource_specs(
1753
- ns_info.dict(by_alias=True), provision_provider=PROVIDER_AWS
1754
+ ns_info.model_dump(by_alias=True), provision_provider=PROVIDER_AWS
1754
1755
  )
1755
1756
  or []
1756
1757
  )
@@ -1808,7 +1809,7 @@ def rds(ctx: click.Context) -> None:
1808
1809
  specs = [
1809
1810
  s
1810
1811
  for s in get_external_resource_specs(
1811
- namespace.dict(by_alias=True), provision_provider=PROVIDER_AWS
1812
+ namespace.model_dump(by_alias=True), provision_provider=PROVIDER_AWS
1812
1813
  )
1813
1814
  if s.provider == "rds"
1814
1815
  ]
@@ -2274,7 +2275,7 @@ def app_interface_merge_queue(ctx: click.Context) -> None:
2274
2275
  "labels",
2275
2276
  ]
2276
2277
  merge_queue_data = []
2277
- now = datetime.utcnow()
2278
+ now = utc_now()
2278
2279
  for mr in merge_requests:
2279
2280
  item = {
2280
2281
  "id": f"[{mr['mr'].iid}]({mr['mr'].web_url})",
@@ -2283,7 +2284,7 @@ def app_interface_merge_queue(ctx: click.Context) -> None:
2283
2284
  + 1, # adding 1 for human readability
2284
2285
  "approved_at": mr["approved_at"],
2285
2286
  "approved_span_minutes": (
2286
- now - datetime.strptime(mr["approved_at"], glhk.DATE_FORMAT)
2287
+ now - from_utc_iso_format(mr["approved_at"])
2287
2288
  ).total_seconds()
2288
2289
  / 60,
2289
2290
  "approved_by": mr["approved_by"],
@@ -2697,7 +2698,7 @@ def ec2_jenkins_workers(
2697
2698
  client = boto3.client("autoscaling")
2698
2699
  ec2 = boto3.resource("ec2")
2699
2700
  results = []
2700
- now = datetime.now(UTC)
2701
+ now = utc_now()
2701
2702
  columns = [
2702
2703
  "type",
2703
2704
  "id",
@@ -2957,11 +2958,11 @@ def osd_component_versions(ctx: click.Context) -> None:
2957
2958
  @get.command()
2958
2959
  @click.pass_context
2959
2960
  def maintenances(ctx: click.Context) -> None:
2960
- now = datetime.now(UTC)
2961
+ now = utc_now()
2961
2962
  maintenances = maintenances_gql.query(gql.get_api().query).maintenances or []
2962
2963
  data = [
2963
2964
  {
2964
- **m.dict(),
2965
+ **m.model_dump(),
2965
2966
  "services": ", ".join(a.name for a in m.affected_services),
2966
2967
  }
2967
2968
  for m in maintenances
@@ -4099,7 +4100,9 @@ def sre_checkpoint_metadata(
4099
4100
  ) -> None:
4100
4101
  """Check an app path for checkpoint-related metadata."""
4101
4102
  data = queries.get_app_metadata(app_path)
4102
- settings = queries.get_app_interface_settings()
4103
+ vault_settings = get_app_interface_vault_settings()
4104
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
4105
+
4103
4106
  app = data[0]
4104
4107
 
4105
4108
  if jiradef:
@@ -4112,7 +4115,14 @@ def sre_checkpoint_metadata(
4112
4115
  # Overrides for easier testing
4113
4116
  if jiraboard:
4114
4117
  board["name"] = jiraboard
4115
- report_invalid_metadata(app, app_path, board, settings, parent_ticket, dry_run)
4118
+ report_invalid_metadata(
4119
+ app=app,
4120
+ path=app_path,
4121
+ board=board,
4122
+ secret_reader=secret_reader,
4123
+ parent=parent_ticket,
4124
+ dry_run=dry_run,
4125
+ )
4116
4126
 
4117
4127
 
4118
4128
  @root.command()
@@ -4289,7 +4299,7 @@ def create(
4289
4299
  bg="red",
4290
4300
  fg="white",
4291
4301
  )
4292
- print(sso_client.json(by_alias=True, indent=2))
4302
+ print(json_dumps(sso_client, indent=2))
4293
4303
 
4294
4304
 
4295
4305
  @sso_client.command()
@@ -4841,11 +4851,12 @@ def top_talkers(ctx: click.Context, top: int) -> None:
4841
4851
  assert project.organization # make mypy happy
4842
4852
  assert project.pk # make mypy happy
4843
4853
 
4854
+ now = utc_now()
4844
4855
  stat = client.project_statistics(
4845
4856
  organization_slug=project.organization.slug,
4846
4857
  project_pk=project.pk,
4847
- start=datetime.now(tz=UTC) - timedelta(hours=24),
4848
- end=datetime.now(tz=UTC),
4858
+ start=now - timedelta(hours=24),
4859
+ end=now,
4849
4860
  )
4850
4861
  stats.append((project, stat))
4851
4862
 
@@ -67,7 +67,9 @@ def main(templates: tuple[str]) -> None:
67
67
  tests.append(test_yaml)
68
68
 
69
69
  template_raw["templateTest"] = tests
70
- template: TemplateV1 = TemplateV1(**data_default_none(TemplateV1, template_raw))
70
+ data = data_default_none(TemplateV1, template_raw)
71
+ assert isinstance(data, dict)
72
+ template: TemplateV1 = TemplateV1(**data)
71
73
 
72
74
  # templates_to_validate = {}
73
75
  for test in template.template_test: