qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__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 (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,4 @@
1
1
  import hashlib
2
- import json
3
2
  from abc import (
4
3
  ABC,
5
4
  )
@@ -23,6 +22,7 @@ from reconcile.gql_definitions.external_resources.external_resources_namespaces
23
22
  NamespaceTerraformResourceElastiCacheV1,
24
23
  NamespaceTerraformResourceKMSV1,
25
24
  NamespaceTerraformResourceMskV1,
25
+ NamespaceTerraformResourceRDSProxyV1,
26
26
  NamespaceTerraformResourceRDSV1,
27
27
  NamespaceV1,
28
28
  )
@@ -37,6 +37,7 @@ from reconcile.utils.exceptions import FetchResourceError
37
37
  from reconcile.utils.external_resource_spec import (
38
38
  ExternalResourceSpec,
39
39
  )
40
+ from reconcile.utils.json import json_dumps
40
41
 
41
42
 
42
43
  class ExternalResourceOrphanedResourcesError(Exception):
@@ -88,9 +89,7 @@ class ExternalResourceKey(BaseModel, frozen=True):
88
89
  )
89
90
 
90
91
  def hash(self) -> str:
91
- return hashlib.md5(
92
- json.dumps(self.dict(), sort_keys=True).encode("utf-8")
93
- ).hexdigest()
92
+ return hashlib.md5(json_dumps(self.model_dump()).encode("utf-8")).hexdigest()
94
93
 
95
94
  @property
96
95
  def state_path(self) -> str:
@@ -104,6 +103,7 @@ SUPPORTED_RESOURCE_TYPES = (
104
103
  | NamespaceTerraformResourceElastiCacheV1
105
104
  | NamespaceTerraformResourceKMSV1
106
105
  | NamespaceTerraformResourceCloudWatchV1
106
+ | NamespaceTerraformResourceRDSProxyV1
107
107
  )
108
108
 
109
109
 
@@ -115,7 +115,9 @@ class ExternalResourcesInventory(MutableMapping):
115
115
  (rp, ns)
116
116
  for ns in namespaces
117
117
  for rp in ns.external_resources or []
118
- if isinstance(rp, SUPPORTED_RESOURCE_PROVIDERS) and rp.resources
118
+ if isinstance(rp, SUPPORTED_RESOURCE_PROVIDERS)
119
+ and rp.resources
120
+ and ns.managed_external_resources
119
121
  ]
120
122
 
121
123
  desired_specs = [
@@ -136,17 +138,17 @@ class ExternalResourcesInventory(MutableMapping):
136
138
  ) -> ExternalResourceSpec:
137
139
  spec = ExternalResourceSpec(
138
140
  provision_provider=provider.provider,
139
- provisioner=provider.provisioner.dict(),
140
- resource=resource.dict(
141
+ provisioner=provider.provisioner.model_dump(),
142
+ resource=resource.model_dump(
141
143
  exclude={
142
144
  FLAG_RESOURCE_MANAGED_BY_ERV2,
143
145
  FLAG_DELETE_RESOURCE,
144
146
  MODULE_OVERRIDES,
145
147
  }
146
148
  ),
147
- namespace=namespace.dict(),
149
+ namespace=namespace.model_dump(by_alias=True),
148
150
  )
149
- spec.metadata[FLAG_DELETE_RESOURCE] = resource.delete
151
+ spec.metadata[FLAG_DELETE_RESOURCE] = resource.delete or namespace.delete
150
152
  spec.metadata[MODULE_OVERRIDES] = resource.module_overrides
151
153
  return spec
152
154
 
@@ -385,7 +387,7 @@ class Reconciliation(BaseModel, frozen=True):
385
387
  )
386
388
  # linked_resources store dependants resources. They will get reconciled
387
389
  # every time the parent resource reconciliation finishes.
388
- linked_resources: frozenset[ExternalResourceKey] | None
390
+ linked_resources: frozenset[ExternalResourceKey] | None = None
389
391
 
390
392
 
391
393
  class ReconcileAction(StrEnum):
@@ -438,6 +440,4 @@ class ExternalResource(BaseModel):
438
440
  provision: ExternalResourceProvision
439
441
 
440
442
  def hash(self) -> str:
441
- return hashlib.md5(
442
- json.dumps(self.data, sort_keys=True).encode("utf-8")
443
- ).hexdigest()
443
+ return hashlib.sha256(json_dumps(self.data).encode("utf-8")).hexdigest()
@@ -70,10 +70,13 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
70
70
  dry_run_suffix: str = ""
71
71
 
72
72
  def name_prefix(self) -> str:
73
+ identifier = (
74
+ f"{self.reconciliation.key.provider}-{self.reconciliation.key.identifier}"
75
+ )
73
76
  if self.is_dry_run:
74
- return f"er-dry-run-mr-{self.dry_run_suffix}"
77
+ return f"er-dry-run-mr-{self.dry_run_suffix}-{identifier}"
75
78
  else:
76
- return "er"
79
+ return f"er-{identifier}"
77
80
 
78
81
  def unit_of_work_identity(self) -> Any:
79
82
  return self.reconciliation.key
@@ -96,10 +99,10 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
96
99
  image=self.reconciliation.module_configuration.image_version,
97
100
  image_pull_policy="Always",
98
101
  resources=V1ResourceRequirements(
99
- requests=self.reconciliation.module_configuration.resources.requests.dict(
102
+ requests=self.reconciliation.module_configuration.resources.requests.model_dump(
100
103
  exclude_none=True
101
104
  ),
102
- limits=self.reconciliation.module_configuration.resources.limits.dict(
105
+ limits=self.reconciliation.module_configuration.resources.limits.model_dump(
103
106
  exclude_none=True
104
107
  ),
105
108
  ),
@@ -3,7 +3,6 @@ import json
3
3
  import logging
4
4
  from abc import abstractmethod
5
5
  from collections.abc import Iterable, Mapping
6
- from datetime import UTC, datetime
7
6
  from hashlib import shake_128
8
7
  from typing import Any
9
8
 
@@ -18,17 +17,18 @@ from reconcile.external_resources.meta import (
18
17
  SECRET_ANN_PROVISION_PROVIDER,
19
18
  SECRET_ANN_PROVISIONER,
20
19
  SECRET_UPDATED_AT,
21
- SECRET_UPDATED_AT_TIMEFORMAT,
22
20
  )
23
21
  from reconcile.external_resources.model import (
24
22
  ExternalResourceKey,
25
23
  )
26
24
  from reconcile.openshift_base import ApplyOptions, apply_action
27
25
  from reconcile.typed_queries.clusters_minimal import get_clusters_minimal
26
+ from reconcile.utils.datetime_util import to_utc_seconds_iso_format, utc_now
28
27
  from reconcile.utils.differ import diff_mappings
29
28
  from reconcile.utils.external_resource_spec import (
30
29
  ExternalResourceSpec,
31
30
  )
31
+ from reconcile.utils.json import json_dumps
32
32
  from reconcile.utils.oc import (
33
33
  OCCli,
34
34
  )
@@ -46,8 +46,8 @@ class VaultSecret(BaseModel):
46
46
 
47
47
  path: str
48
48
  field: str
49
- version: int | None
50
- q_format: str | None
49
+ version: int | None = None
50
+ q_format: str | None = None
51
51
 
52
52
 
53
53
  class SecretHelper:
@@ -154,7 +154,7 @@ class SecretsReconciler:
154
154
  annotations[SECRET_ANN_PROVIDER] = spec.provider
155
155
  annotations[SECRET_ANN_IDENTIFIER] = spec.identifier
156
156
  annotations[SECRET_UPDATED_AT] = spec.metadata[SECRET_UPDATED_AT]
157
- spec.resource["annotations"] = json.dumps(annotations)
157
+ spec.resource["annotations"] = json_dumps(annotations)
158
158
 
159
159
  def _specs_with_secret(
160
160
  self,
@@ -351,9 +351,7 @@ class InClusterSecretsReconciler(SecretsReconciler):
351
351
  secret_name = secret["metadata"]["name"]
352
352
  spec = secrets_map[secret_name]
353
353
  spec.secret = self.output_secrets_formatter.format(secret["data"])
354
- spec.metadata[SECRET_UPDATED_AT] = datetime.now(UTC).strftime(
355
- SECRET_UPDATED_AT_TIMEFORMAT
356
- )
354
+ spec.metadata[SECRET_UPDATED_AT] = to_utc_seconds_iso_format(utc_now())
357
355
 
358
356
  def _delete_source_secret(self, spec: ExternalResourceSpec) -> None:
359
357
  secret_name = self._get_spec_outputs_secret_name(spec)
@@ -1,7 +1,9 @@
1
+ import json
1
2
  import logging
2
3
  from collections.abc import Mapping
3
- from datetime import UTC, datetime
4
+ from datetime import datetime
4
5
  from enum import StrEnum
6
+ from hashlib import sha256
5
7
  from typing import Any
6
8
 
7
9
  from pydantic import BaseModel
@@ -16,6 +18,8 @@ from reconcile.external_resources.model import (
16
18
  ResourceStatus,
17
19
  )
18
20
  from reconcile.utils.aws_api_typed.api import AWSApi
21
+ from reconcile.utils.datetime_util import to_utc_microseconds_iso_format, utc_now
22
+ from reconcile.utils.json import json_dumps
19
23
 
20
24
  DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
21
25
 
@@ -41,7 +45,7 @@ class ExternalResourceState(BaseModel):
41
45
  self, reconciliation_status: ReconciliationStatus
42
46
  ) -> None:
43
47
  if self.reconciliation_needs_state_update(reconciliation_status):
44
- self.ts = datetime.now()
48
+ self.ts = utc_now()
45
49
  self.resource_status = reconciliation_status.resource_status
46
50
 
47
51
  def reconciliation_needs_state_update(
@@ -169,8 +173,8 @@ class DynamoDBStateAdapter:
169
173
 
170
174
  def serialize(self, state: ExternalResourceState) -> dict[str, Any]:
171
175
  return {
172
- self.ER_KEY_HASH: {"S": state.key.hash()},
173
- self.TIMESTAMP: {"S": state.ts.isoformat()},
176
+ self.ER_KEY_HASH: {"S": state.key.state_path},
177
+ self.TIMESTAMP: {"S": to_utc_microseconds_iso_format(state.ts)},
174
178
  self.RESOURCE_STATUS: {"S": state.resource_status.value},
175
179
  self.ER_KEY: {
176
180
  "M": {
@@ -258,24 +262,63 @@ class ExternalResourcesStateDynamoDB:
258
262
  self._table = table_name
259
263
  self.partial_resources = self._get_partial_resources()
260
264
 
265
+ def _new_sha256_hash(self, item: dict) -> str:
266
+ resource_json = item[self.adapter.RECONC]["M"][self.adapter.RECONC_INPUT]["S"]
267
+ resource_dict = json.loads(resource_json)
268
+ data = resource_dict["data"]
269
+ return sha256(json_dumps(data).encode("utf-8")).hexdigest()
270
+
261
271
  def get_external_resource_state(
262
- self, key: ExternalResourceKey
272
+ self,
273
+ key: ExternalResourceKey,
274
+ enable_migration: bool = False,
263
275
  ) -> ExternalResourceState:
264
276
  data = self.aws_api.dynamodb.boto3_client.get_item(
265
277
  TableName=self._table,
266
278
  ConsistentRead=True,
267
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
279
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
268
280
  )
269
- if "Item" in data:
281
+ item = data.get("Item")
282
+ if item:
270
283
  return self.adapter.deserialize(data["Item"])
271
- else:
272
- return ExternalResourceState(
273
- key=key,
274
- ts=datetime.now(UTC),
275
- resource_status=ResourceStatus.NOT_EXISTS,
276
- reconciliation=Reconciliation(key=key),
277
- reconciliation_errors=0,
278
- )
284
+
285
+ old_data = self.aws_api.dynamodb.boto3_client.get_item(
286
+ TableName=self._table,
287
+ ConsistentRead=True,
288
+ Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
289
+ )
290
+ old_item = old_data.get("Item")
291
+ if old_item:
292
+ old_item[self.adapter.ER_KEY_HASH]["S"] = key.state_path
293
+ old_item[self.adapter.RECONC]["M"][self.adapter.RECONC_RESOURCE_HASH][
294
+ "S"
295
+ ] = self._new_sha256_hash(old_item)
296
+ if enable_migration:
297
+ self.aws_api.dynamodb.boto3_client.transact_write_items(
298
+ TransactItems=[
299
+ {
300
+ "Put": {
301
+ "TableName": self._table,
302
+ "Item": old_item,
303
+ }
304
+ },
305
+ {
306
+ "Delete": {
307
+ "TableName": self._table,
308
+ "Key": {self.adapter.ER_KEY_HASH: {"S": key.hash()}},
309
+ }
310
+ },
311
+ ]
312
+ )
313
+ return self.adapter.deserialize(old_item)
314
+
315
+ return ExternalResourceState(
316
+ key=key,
317
+ ts=utc_now(),
318
+ resource_status=ResourceStatus.NOT_EXISTS,
319
+ reconciliation=Reconciliation(key=key),
320
+ reconciliation_errors=0,
321
+ )
279
322
 
280
323
  def set_external_resource_state(
281
324
  self,
@@ -288,7 +331,7 @@ class ExternalResourcesStateDynamoDB:
288
331
  def del_external_resource_state(self, key: ExternalResourceKey) -> None:
289
332
  self.aws_api.dynamodb.boto3_client.delete_item(
290
333
  TableName=self._table,
291
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
334
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
292
335
  )
293
336
 
294
337
  def _get_partial_resources(
@@ -330,7 +373,7 @@ class ExternalResourcesStateDynamoDB:
330
373
  ) -> None:
331
374
  self.aws_api.dynamodb.boto3_client.update_item(
332
375
  TableName=self._table,
333
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
376
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
334
377
  UpdateExpression="set resource_status=:new_value",
335
378
  ExpressionAttributeValues={":new_value": {"S": status.value}},
336
379
  ReturnValues="UPDATED_NEW",
@@ -58,7 +58,7 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
58
58
  """Return the desired state for early exit."""
59
59
  return {
60
60
  "version": QONTRACT_INTEGRATION_VERSION,
61
- "specs": {spec.name: spec.dict() for spec in get_fleet_label_specs()},
61
+ "specs": {spec.name: spec.model_dump() for spec in get_fleet_label_specs()},
62
62
  }
63
63
 
64
64
  def run(self, dry_run: bool) -> None:
@@ -1,4 +1,3 @@
1
- import json
2
1
  import logging
3
2
  import sys
4
3
  from collections.abc import (
@@ -17,9 +16,11 @@ from reconcile import queries
17
16
  from reconcile.status import ExitCodes
18
17
  from reconcile.utils.aggregated_list import RunnerError
19
18
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
19
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
20
20
  from reconcile.utils.defer import defer
21
21
  from reconcile.utils.disabled_integrations import integration_is_enabled
22
22
  from reconcile.utils.external_resources import get_external_resource_specs
23
+ from reconcile.utils.json import json_dumps
23
24
  from reconcile.utils.openshift_resource import (
24
25
  OpenshiftResource,
25
26
  ResourceInventory,
@@ -39,12 +40,12 @@ def construct_gabi_oc_resource(
39
40
  "kind": "ConfigMap",
40
41
  "metadata": {"name": name, "annotations": {"qontract.recycle": "true"}},
41
42
  "data": {
42
- "config.json": json.dumps(
43
+ "config.json": json_dumps(
43
44
  {
44
45
  "expiration": str(expiration_date),
45
46
  "users": users,
46
47
  },
47
- separators=(",", ":"),
48
+ compact=True,
48
49
  ),
49
50
  },
50
51
  }
@@ -65,8 +66,10 @@ def fetch_desired_state(
65
66
  gabi_instances: Iterable[Mapping], ri: ResourceInventory
66
67
  ) -> None:
67
68
  for g in gabi_instances:
68
- expiration_date = datetime.strptime(g["expirationDate"], "%Y-%m-%d").date()
69
- if (expiration_date - date.today()).days > EXPIRATION_DAYS_MAX:
69
+ expiration_date = ensure_utc(
70
+ datetime.strptime(g["expirationDate"], "%Y-%m-%d") # noqa: DTZ007
71
+ ).date()
72
+ if (expiration_date - utc_now().date()).days > EXPIRATION_DAYS_MAX:
70
73
  raise RunnerError(
71
74
  f"The maximum expiration date of {g['name']} shall not "
72
75
  f"exceed {EXPIRATION_DAYS_MAX} days from today"
@@ -132,7 +132,7 @@ class QuayMirror:
132
132
  mirror_creds = None
133
133
  pull_credentials = item.mirror.pull_credentials
134
134
  if pull_credentials:
135
- raw_data = self.secret_reader.read_all(pull_credentials.dict())
135
+ raw_data = self.secret_reader.read_all(pull_credentials.model_dump())
136
136
  username = raw_data["user"]
137
137
  password = raw_data["token"]
138
138
  mirror_creds = f"{username}:{password}"
@@ -226,7 +226,7 @@ class QuayMirror:
226
226
  return False
227
227
 
228
228
  def _decode_push_secret(self, secret: VaultSecret) -> str:
229
- raw_data = self.secret_reader.read_all(secret.dict())
229
+ raw_data = self.secret_reader.read_all(secret.model_dump())
230
230
  token = base64.b64decode(raw_data["token"]).decode()
231
231
  return f"{raw_data['user']}:{token}"
232
232
 
reconcile/github_org.py CHANGED
@@ -144,7 +144,7 @@ def get_org_and_teams(
144
144
 
145
145
 
146
146
  @retry()
147
- def get_members(unit: Organization) -> list[str]:
147
+ def get_members(unit: Organization | Team) -> list[str]:
148
148
  return [member.login for member in unit.get_members()]
149
149
 
150
150
 
@@ -2,6 +2,7 @@ import logging
2
2
  import os
3
3
 
4
4
  import github
5
+ import github.NamedUser
5
6
  from github import Github
6
7
  from sretoolbox.utils import retry
7
8
 
@@ -100,4 +101,7 @@ def run(dry_run: bool) -> None:
100
101
 
101
102
  if not dry_run:
102
103
  gh_user = gh.get_user(github_username)
104
+ assert isinstance(
105
+ gh_user, github.NamedUser.NamedUser
106
+ ) # make mypy happy
103
107
  gh_org.add_to_members(gh_user, "admin")
@@ -6,7 +6,6 @@ from collections.abc import (
6
6
  from contextlib import suppress
7
7
  from dataclasses import dataclass
8
8
  from datetime import (
9
- UTC,
10
9
  datetime,
11
10
  timedelta,
12
11
  )
@@ -30,6 +29,7 @@ from sretoolbox.utils import retry
30
29
 
31
30
  from reconcile import queries
32
31
  from reconcile.change_owners.change_types import ChangeTypePriority
32
+ from reconcile.utils.datetime_util import ensure_utc, from_utc_iso_format, utc_now
33
33
  from reconcile.utils.gitlab_api import (
34
34
  GitLabApi,
35
35
  MRState,
@@ -72,7 +72,6 @@ HOLD_LABELS = [
72
72
  ]
73
73
 
74
74
  QONTRACT_INTEGRATION = "gitlab-housekeeping"
75
- DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
76
75
  EXPIRATION_DATE_FORMAT = "%Y-%m-%d"
77
76
  SQUASH_OPTION_ALWAYS = "always"
78
77
 
@@ -128,9 +127,7 @@ def _calculate_time_since_approval(approved_at: str) -> float:
128
127
  Returns the number of minutes since a MR has been approved.
129
128
  :param approved_at: the datetime the MR was approved in format %Y-%m-%dT%H:%M:%S.%fZ
130
129
  """
131
- time_since_approval = datetime.utcnow() - datetime.strptime(
132
- approved_at, DATE_FORMAT
133
- )
130
+ time_since_approval = utc_now() - from_utc_iso_format(approved_at)
134
131
  return time_since_approval.total_seconds() / 60
135
132
 
136
133
 
@@ -138,7 +135,7 @@ def get_timed_out_pipelines(
138
135
  pipelines: list[ProjectMergeRequestPipeline],
139
136
  pipeline_timeout: int = 60,
140
137
  ) -> list[ProjectMergeRequestPipeline]:
141
- now = datetime.utcnow()
138
+ now = utc_now()
142
139
 
143
140
  pending_pipelines = [
144
141
  p
@@ -152,7 +149,7 @@ def get_timed_out_pipelines(
152
149
  timed_out_pipelines = []
153
150
 
154
151
  for p in pending_pipelines:
155
- update_time = datetime.strptime(p.updated_at, DATE_FORMAT)
152
+ update_time = from_utc_iso_format(p.updated_at)
156
153
 
157
154
  elapsed = (now - update_time).total_seconds()
158
155
 
@@ -279,7 +276,7 @@ def handle_stale_items(
279
276
  ) -> None:
280
277
  LABEL = "stale" # noqa: N806
281
278
 
282
- now = datetime.utcnow()
279
+ now = utc_now()
283
280
  for item in items:
284
281
  if AUTO_MERGE in item.labels:
285
282
  if item.merge_status == MRStatus.UNCHECKED:
@@ -287,7 +284,7 @@ def handle_stale_items(
287
284
  item = gl.get_merge_request(item.iid)
288
285
  if item.merge_status == MRStatus.CANNOT_BE_MERGED:
289
286
  close_item(dry_run, gl, enable_closing, item_type, item)
290
- update_date = datetime.strptime(item.updated_at, DATE_FORMAT)
287
+ update_date = from_utc_iso_format(item.updated_at)
291
288
 
292
289
  # if item is over days_interval
293
290
  current_interval = now.date() - update_date.date()
@@ -315,10 +312,9 @@ def handle_stale_items(
315
312
  if not cancel_notes:
316
313
  continue
317
314
 
318
- cancel_notes_dates = [
319
- datetime.strptime(item.updated_at, DATE_FORMAT) for note in cancel_notes
320
- ]
321
- latest_cancel_note_date = max(d for d in cancel_notes_dates)
315
+ latest_cancel_note_date = max(
316
+ from_utc_iso_format(note.updated_at) for note in cancel_notes
317
+ )
322
318
  # if the latest cancel note is under
323
319
  # days_interval - remove 'stale' label
324
320
  current_interval = now.date() - latest_cancel_note_date.date()
@@ -653,8 +649,10 @@ def publish_access_token_expiration_metrics(gl: GitLabApi) -> None:
653
649
 
654
650
  for pat in pats:
655
651
  if pat.active:
656
- expiration_date = datetime.strptime(pat.expires_at, EXPIRATION_DATE_FORMAT)
657
- days_until_expiration = expiration_date.date() - datetime.now(UTC).date()
652
+ expiration_date = ensure_utc(
653
+ datetime.strptime(pat.expires_at, EXPIRATION_DATE_FORMAT) # noqa: DTZ007
654
+ )
655
+ days_until_expiration = expiration_date.date() - utc_now().date()
658
656
  gitlab_token_expiration.labels(pat.name).set(days_until_expiration.days)
659
657
  else:
660
658
  with suppress(KeyError, ValueError):
@@ -44,19 +44,13 @@ class GitlabUser(BaseModel):
44
44
  access_level: int
45
45
 
46
46
 
47
- class CurrentStateSpec(BaseModel):
47
+ class CurrentStateSpec(BaseModel, arbitrary_types_allowed=True):
48
48
  members: dict[str, GroupMember]
49
49
 
50
- class Config:
51
- arbitrary_types_allowed = True
52
50
 
53
-
54
- class DesiredStateSpec(BaseModel):
51
+ class DesiredStateSpec(BaseModel, arbitrary_types_allowed=True):
55
52
  members: dict[str, GitlabUser]
56
53
 
57
- class Config:
58
- arbitrary_types_allowed = True
59
-
60
54
 
61
55
  CurrentState = dict[str, CurrentStateSpec]
62
56
  DesiredState = dict[str, DesiredStateSpec]
@@ -122,8 +116,8 @@ def build_desired_state_spec(
122
116
  pagerduty_map,
123
117
  get_username_method=lambda u: u.org_username,
124
118
  )
125
- for u in usernames_from_pagerduty:
126
- gu = GitlabUser(user=u, access_level=p_access_level)
119
+ for pu in usernames_from_pagerduty:
120
+ gu = GitlabUser(user=pu, access_level=p_access_level)
127
121
  add_or_update_user(desired_state_spec, gu)
128
122
  return desired_state_spec
129
123
 
@@ -239,6 +233,6 @@ def reconcile_gitlab_members(
239
233
  def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
240
234
  gqlapi = gql.get_api()
241
235
  return {
242
- "instance": get_gitlab_instance(gqlapi.query).dict(),
243
- "permissions": [p.dict() for p in get_permissions(gqlapi.query)],
236
+ "instance": get_gitlab_instance(gqlapi.query).model_dump(),
237
+ "permissions": [p.model_dump() for p in get_permissions(gqlapi.query)],
244
238
  }
@@ -2,7 +2,6 @@
2
2
  SQS Consumer to create Gitlab merge requests.
3
3
  """
4
4
 
5
- import json
6
5
  import logging
7
6
  import sys
8
7
  from collections.abc import Callable
@@ -11,6 +10,7 @@ from reconcile import queries
11
10
  from reconcile.utils import mr
12
11
  from reconcile.utils.defer import defer
13
12
  from reconcile.utils.gitlab_api import GitLabApi
13
+ from reconcile.utils.json import json_dumps
14
14
  from reconcile.utils.secret_reader import SecretReader
15
15
  from reconcile.utils.sqs_gateway import SQSGateway
16
16
 
@@ -51,7 +51,7 @@ def run(dry_run: str, gitlab_project_id: str, defer: Callable | None = None) ->
51
51
  for m in messages:
52
52
  receipt_handle, body = m[0], m[1]
53
53
  logging.info(
54
- "received message %s with body %s", receipt_handle[:6], json.dumps(body)
54
+ "received message %s with body %s", receipt_handle[:6], json_dumps(body)
55
55
  )
56
56
 
57
57
  if not dry_run:
@@ -2,12 +2,12 @@ import logging
2
2
  from collections.abc import Callable, Mapping
3
3
  from typing import Any
4
4
 
5
- from dateutil import parser as dateparser
6
5
  from gitlab.v4.objects import ProjectMergeRequest
7
6
  from sretoolbox.utils import threaded
8
7
 
9
8
  from reconcile import queries
10
9
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
10
+ from reconcile.utils.datetime_util import from_utc_iso_format
11
11
  from reconcile.utils.defer import defer
12
12
  from reconcile.utils.gitlab_api import (
13
13
  GitLabApi,
@@ -49,12 +49,14 @@ class MRApproval:
49
49
  self.dry_run = dry_run
50
50
  self.persistent_lgtm = persistent_lgtm
51
51
 
52
- # Get the date of the most recent commit (top commit) in the MR, but avoid comparing against None
53
- self.top_commit_created_at = dateparser.parse("2000-01-01")
54
- commits = self.mr.commits()
55
- if commits:
56
- top_commit = next(commits)
57
- self.top_commit_created_at = dateparser.parse(top_commit.created_at)
52
+ # Get the date of the most recent commit (top commit) in the MR
53
+ self.top_commit_created_at = next(
54
+ (
55
+ from_utc_iso_format(commit.created_at)
56
+ for commit in merge_request.commits()
57
+ ),
58
+ None,
59
+ )
58
60
 
59
61
  def get_change_owners_map(self) -> dict[str, dict[str, list[str]]]:
60
62
  """
@@ -95,9 +97,10 @@ class MRApproval:
95
97
 
96
98
  # Only interested in comments created after the top commit
97
99
  # creation time
98
- comment_created_at = dateparser.parse(comment.created_at)
100
+ comment_created_at = from_utc_iso_format(comment.created_at)
99
101
  if (
100
- comment_created_at < self.top_commit_created_at
102
+ self.top_commit_created_at is not None
103
+ and comment_created_at < self.top_commit_created_at
101
104
  and not self.persistent_lgtm
102
105
  ):
103
106
  continue
@@ -185,9 +188,10 @@ class MRApproval:
185
188
  # If the comment was created before the last commit,
186
189
  # it means we had a push after the comment. In this case,
187
190
  # we delete the comment and move on.
188
- comment_created_at = dateparser.parse(comment.created_at)
191
+ comment_created_at = from_utc_iso_format(comment.created_at)
189
192
  if (
190
- comment_created_at < self.top_commit_created_at
193
+ self.top_commit_created_at is not None
194
+ and comment_created_at < self.top_commit_created_at
191
195
  and comment.note is not None
192
196
  ):
193
197
  # Deleting stale comments
@@ -94,14 +94,6 @@ class GroupPermissionHandler:
94
94
  desired_state: dict[str, GroupSpec],
95
95
  current_state: dict[str, GroupSpec],
96
96
  ) -> None:
97
- # gather list of app-interface managed repos
98
- instance = queries.get_gitlab_instance()
99
- managed_repos = {
100
- f"{instance['url']}/{project_request['group']}/{r}"
101
- for project_request in instance.get("projectRequests", [])
102
- for r in project_request.get("projects", [])
103
- }
104
-
105
97
  # get the diff data
106
98
  diff_data = diff_mappings(
107
99
  current=current_state,
@@ -112,11 +104,10 @@ class GroupPermissionHandler:
112
104
  errors: list[Exception] = []
113
105
  for repo in diff_data.add:
114
106
  project = self.gl.get_project(repo)
115
- if not project and repo in managed_repos:
116
- logging.info(
117
- f"New app-interface managed repository {repo} hasn't been created yet - skipping"
118
- )
107
+ if not project:
108
+ logging.info(f"{repo} hasn't been created yet - skipping")
119
109
  continue
110
+
120
111
  if not self.can_share_project(project):
121
112
  errors.append(
122
113
  GroupAccessLevelError(
@@ -136,8 +127,13 @@ class GroupPermissionHandler:
136
127
  group_id=self.group.id,
137
128
  access_level=self.access_level,
138
129
  )
130
+
139
131
  for repo in diff_data.change:
140
132
  project = self.gl.get_project(repo)
133
+ if not project:
134
+ logging.info(f"{repo} hasn't been created yet - skipping")
135
+ continue
136
+
141
137
  if not self.can_share_project(project):
142
138
  errors.append(
143
139
  GroupAccessLevelError(