qontract-reconcile 0.10.2.dev334__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 (370) hide show
  1. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +366 -361
  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 +3 -3
  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 +6 -12
  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 +11 -3
  28. reconcile/cli.py +111 -18
  29. reconcile/dashdotdb_dora.py +5 -12
  30. reconcile/dashdotdb_slo.py +1 -1
  31. reconcile/database_access_manager.py +123 -117
  32. reconcile/dynatrace_token_provider/integration.py +1 -1
  33. reconcile/endpoints_discovery/integration.py +4 -1
  34. reconcile/endpoints_discovery/merge_request.py +1 -1
  35. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  36. reconcile/external_resources/factories.py +5 -12
  37. reconcile/external_resources/integration.py +1 -1
  38. reconcile/external_resources/manager.py +8 -5
  39. reconcile/external_resources/meta.py +0 -1
  40. reconcile/external_resources/metrics.py +1 -1
  41. reconcile/external_resources/model.py +20 -20
  42. reconcile/external_resources/reconciler.py +7 -4
  43. reconcile/external_resources/secrets_sync.py +8 -11
  44. reconcile/external_resources/state.py +26 -16
  45. reconcile/fleet_labeler/integration.py +1 -1
  46. reconcile/gabi_authorized_users.py +8 -5
  47. reconcile/gcp_image_mirror.py +2 -2
  48. reconcile/github_org.py +1 -1
  49. reconcile/github_owners.py +4 -0
  50. reconcile/gitlab_housekeeping.py +13 -15
  51. reconcile/gitlab_members.py +6 -12
  52. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  53. reconcile/gitlab_owners.py +15 -11
  54. reconcile/gitlab_permissions.py +8 -12
  55. reconcile/glitchtip_project_alerts/integration.py +3 -1
  56. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  57. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  58. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  59. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  60. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  61. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  62. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  63. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  64. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  65. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  66. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  67. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  68. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  70. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  71. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  72. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  73. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  74. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  75. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  76. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  77. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  82. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  83. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  84. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  85. reconcile/gql_definitions/common/apps.py +5 -5
  86. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  87. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  88. reconcile/gql_definitions/common/clusters.py +37 -35
  89. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  90. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  91. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  92. reconcile/gql_definitions/common/github_orgs.py +10 -10
  93. reconcile/gql_definitions/common/jira_settings.py +10 -10
  94. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  95. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  96. reconcile/gql_definitions/common/namespaces.py +42 -44
  97. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  98. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  99. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  100. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  101. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  102. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  103. reconcile/gql_definitions/common/quay_instances.py +5 -5
  104. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  105. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  106. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  107. reconcile/gql_definitions/common/saas_files.py +44 -44
  108. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  109. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  110. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  111. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  112. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  113. reconcile/gql_definitions/common/users.py +5 -5
  114. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  115. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  116. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  117. reconcile/gql_definitions/cost_report/settings.py +9 -9
  118. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  119. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  120. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  121. reconcile/gql_definitions/email_sender/apps.py +5 -5
  122. reconcile/gql_definitions/email_sender/emails.py +8 -8
  123. reconcile/gql_definitions/email_sender/users.py +6 -6
  124. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  125. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  126. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  127. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  128. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  129. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  130. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  131. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  133. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  135. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  136. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  137. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  138. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  139. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  140. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  141. reconcile/gql_definitions/fragments/disable.py +5 -5
  142. reconcile/gql_definitions/fragments/email_service.py +5 -5
  143. reconcile/gql_definitions/fragments/email_user.py +5 -5
  144. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  145. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  146. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  147. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  148. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  149. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  150. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  151. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  152. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  154. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  155. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  156. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  157. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  158. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  159. reconcile/gql_definitions/fragments/user.py +5 -5
  160. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  161. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  162. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  163. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  164. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  165. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  166. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  167. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  168. reconcile/gql_definitions/integrations/integrations.py +48 -51
  169. reconcile/gql_definitions/introspection.json +3207 -1683
  170. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  171. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  172. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  173. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  174. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  175. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  176. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  177. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  178. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  179. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  180. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  181. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  182. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  183. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  184. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  185. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  186. reconcile/gql_definitions/rhcs/certs.py +33 -87
  187. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  188. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  189. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  190. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  191. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  192. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  193. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  194. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  195. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  196. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  197. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  198. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  199. reconcile/gql_definitions/status_board/status_board.py +6 -7
  200. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  201. reconcile/gql_definitions/templating/template_collection.py +5 -5
  202. reconcile/gql_definitions/templating/templates.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  204. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  205. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  206. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  207. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  208. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  209. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  210. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  211. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  212. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +440 -407
  213. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  214. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  215. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  216. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  217. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  218. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  219. reconcile/integrations_manager.py +3 -3
  220. reconcile/jenkins_worker_fleets.py +10 -8
  221. reconcile/jira_permissions_validator.py +237 -122
  222. reconcile/ldap_groups/integration.py +1 -1
  223. reconcile/ocm/types.py +35 -56
  224. reconcile/ocm_aws_infrastructure_access.py +1 -1
  225. reconcile/ocm_clusters.py +4 -4
  226. reconcile/ocm_labels/integration.py +3 -2
  227. reconcile/ocm_machine_pools.py +33 -27
  228. reconcile/openshift_base.py +113 -5
  229. reconcile/openshift_cluster_bots.py +3 -2
  230. reconcile/openshift_namespace_labels.py +1 -1
  231. reconcile/openshift_namespaces.py +97 -101
  232. reconcile/openshift_resources_base.py +6 -2
  233. reconcile/openshift_rhcs_certs.py +74 -37
  234. reconcile/openshift_rolebindings.py +230 -130
  235. reconcile/openshift_saas_deploy.py +6 -7
  236. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  237. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  238. reconcile/openshift_serviceaccount_tokens.py +2 -2
  239. reconcile/openshift_upgrade_watcher.py +4 -4
  240. reconcile/openshift_users.py +5 -3
  241. reconcile/oum/labelset.py +5 -3
  242. reconcile/oum/models.py +1 -4
  243. reconcile/prometheus_rules_tester/integration.py +3 -3
  244. reconcile/quay_mirror.py +1 -1
  245. reconcile/queries.py +131 -0
  246. reconcile/rhidp/common.py +3 -5
  247. reconcile/rhidp/sso_client/base.py +16 -5
  248. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  249. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  250. reconcile/skupper_network/integration.py +2 -2
  251. reconcile/slack_usergroups.py +35 -14
  252. reconcile/sql_query.py +1 -0
  253. reconcile/status_board.py +6 -6
  254. reconcile/statuspage/atlassian.py +7 -7
  255. reconcile/statuspage/integrations/maintenances.py +4 -3
  256. reconcile/statuspage/page.py +4 -9
  257. reconcile/statuspage/status.py +5 -8
  258. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  259. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  260. reconcile/templating/lib/merge_request_manager.py +2 -2
  261. reconcile/templating/lib/rendering.py +3 -3
  262. reconcile/templating/renderer.py +12 -13
  263. reconcile/terraform_aws_route53.py +7 -1
  264. reconcile/terraform_cloudflare_dns.py +3 -3
  265. reconcile/terraform_cloudflare_resources.py +5 -5
  266. reconcile/terraform_cloudflare_users.py +3 -2
  267. reconcile/terraform_init/integration.py +187 -23
  268. reconcile/terraform_repo.py +16 -12
  269. reconcile/terraform_resources.py +17 -7
  270. reconcile/terraform_tgw_attachments.py +27 -19
  271. reconcile/terraform_users.py +7 -0
  272. reconcile/terraform_vpc_peerings.py +14 -3
  273. reconcile/terraform_vpc_resources/integration.py +20 -8
  274. reconcile/typed_queries/app_interface_roles.py +10 -0
  275. reconcile/typed_queries/aws_account_tags.py +41 -0
  276. reconcile/typed_queries/cost_report/app_names.py +1 -1
  277. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  278. reconcile/typed_queries/saas_files.py +13 -13
  279. reconcile/typed_queries/status_board.py +2 -2
  280. reconcile/unleash_feature_toggles/integration.py +4 -2
  281. reconcile/utils/acs/base.py +6 -3
  282. reconcile/utils/acs/policies.py +2 -2
  283. reconcile/utils/aggregated_list.py +4 -3
  284. reconcile/utils/aws_api.py +51 -20
  285. reconcile/utils/aws_api_typed/api.py +38 -9
  286. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  287. reconcile/utils/aws_api_typed/logs.py +73 -0
  288. reconcile/utils/aws_api_typed/organization.py +4 -2
  289. reconcile/utils/binary.py +7 -12
  290. reconcile/utils/datetime_util.py +67 -0
  291. reconcile/utils/deadmanssnitch_api.py +1 -1
  292. reconcile/utils/differ.py +2 -3
  293. reconcile/utils/early_exit_cache.py +11 -12
  294. reconcile/utils/expiration.py +7 -3
  295. reconcile/utils/external_resource_spec.py +24 -1
  296. reconcile/utils/filtering.py +1 -1
  297. reconcile/utils/gitlab_api.py +7 -5
  298. reconcile/utils/glitchtip/client.py +6 -2
  299. reconcile/utils/glitchtip/models.py +25 -28
  300. reconcile/utils/gql.py +4 -7
  301. reconcile/utils/helm.py +2 -1
  302. reconcile/utils/helpers.py +1 -1
  303. reconcile/utils/instrumented_wrappers.py +1 -1
  304. reconcile/utils/internal_groups/client.py +2 -2
  305. reconcile/utils/internal_groups/models.py +8 -17
  306. reconcile/utils/jinja2/utils.py +6 -8
  307. reconcile/utils/jira_client.py +82 -63
  308. reconcile/utils/jjb_client.py +28 -15
  309. reconcile/utils/jobcontroller/controller.py +2 -2
  310. reconcile/utils/jobcontroller/models.py +17 -1
  311. reconcile/utils/json.py +74 -0
  312. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  313. reconcile/utils/membershipsources/models.py +16 -23
  314. reconcile/utils/membershipsources/resolver.py +4 -2
  315. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  316. reconcile/utils/merge_request_manager/parser.py +6 -6
  317. reconcile/utils/metrics.py +5 -5
  318. reconcile/utils/models.py +304 -82
  319. reconcile/utils/mr/app_interface_reporter.py +2 -2
  320. reconcile/utils/mr/base.py +2 -2
  321. reconcile/utils/mr/notificator.py +3 -3
  322. reconcile/utils/mr/update_access_report_base.py +3 -4
  323. reconcile/utils/mr/user_maintenance.py +3 -2
  324. reconcile/utils/oc.py +249 -203
  325. reconcile/utils/oc_filters.py +3 -3
  326. reconcile/utils/ocm/addons.py +0 -1
  327. reconcile/utils/ocm/base.py +18 -21
  328. reconcile/utils/ocm/cluster_groups.py +1 -1
  329. reconcile/utils/ocm/identity_providers.py +2 -2
  330. reconcile/utils/ocm/labels.py +1 -1
  331. reconcile/utils/ocm/products.py +9 -3
  332. reconcile/utils/ocm/search_filters.py +3 -6
  333. reconcile/utils/ocm/service_log.py +4 -6
  334. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  335. reconcile/utils/openshift_resource.py +10 -5
  336. reconcile/utils/output.py +3 -2
  337. reconcile/utils/pagerduty_api.py +10 -7
  338. reconcile/utils/promotion_state.py +6 -11
  339. reconcile/utils/raw_github_api.py +1 -1
  340. reconcile/utils/rhcsv2_certs.py +138 -35
  341. reconcile/utils/rosa/session.py +16 -0
  342. reconcile/utils/runtime/integration.py +2 -3
  343. reconcile/utils/runtime/runner.py +2 -2
  344. reconcile/utils/saasherder/interfaces.py +13 -20
  345. reconcile/utils/saasherder/models.py +25 -21
  346. reconcile/utils/saasherder/saasherder.py +55 -31
  347. reconcile/utils/slack_api.py +26 -4
  348. reconcile/utils/sloth.py +224 -0
  349. reconcile/utils/sqs_gateway.py +2 -1
  350. reconcile/utils/state.py +2 -1
  351. reconcile/utils/structs.py +1 -1
  352. reconcile/utils/terraform_client.py +5 -4
  353. reconcile/utils/terrascript_aws_client.py +192 -114
  354. reconcile/utils/unleash/server.py +2 -8
  355. reconcile/utils/vault.py +5 -12
  356. reconcile/utils/vcs.py +8 -8
  357. reconcile/vault_replication.py +107 -42
  358. tools/app_interface_reporter.py +4 -4
  359. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  360. tools/cli_commands/cost_report/view.py +7 -6
  361. tools/cli_commands/erv2.py +1 -1
  362. tools/cli_commands/systems_and_tools.py +5 -1
  363. tools/qontract_cli.py +31 -18
  364. tools/template_validation.py +3 -1
  365. reconcile/gql_definitions/cna/__init__.py +0 -0
  366. reconcile/gql_definitions/cna/queries/__init__.py +0 -0
  367. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  368. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  369. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  370. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,7 @@ class Scope(BaseModel):
11
11
  """
12
12
 
13
13
  cluster: str
14
- namespace: str | None
14
+ namespace: str | None = None
15
15
 
16
16
 
17
17
  class PolicyCondition(BaseModel):
@@ -23,7 +23,7 @@ class PolicyCondition(BaseModel):
23
23
  """
24
24
 
25
25
  field_name: str
26
- negate: bool | None
26
+ negate: bool | None = None
27
27
  values: list[str]
28
28
 
29
29
 
@@ -1,8 +1,9 @@
1
- import json
2
1
  import logging
3
2
  from collections.abc import Callable, KeysView
4
3
  from typing import Any, TypedDict
5
4
 
5
+ from reconcile.utils.json import json_dumps
6
+
6
7
  Action = Callable[[Any, list[Any]], bool]
7
8
  Cond = Callable[[Any], bool]
8
9
 
@@ -89,11 +90,11 @@ class AggregatedList:
89
90
  return list(self._dict.values())
90
91
 
91
92
  def to_json(self) -> str:
92
- return json.dumps(self.dump(), indent=4)
93
+ return json_dumps(self.dump(), indent=4)
93
94
 
94
95
  @staticmethod
95
96
  def hash_params(params: Any) -> int:
96
- return hash(json.dumps(params, sort_keys=True))
97
+ return hash(json_dumps(params))
97
98
 
98
99
 
99
100
  class AggregatedDiffRunner:
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import logging
4
4
  import operator
5
5
  import os
6
- import re
7
6
  from functools import lru_cache
8
7
  from threading import Lock
9
8
  from typing import (
@@ -25,6 +24,7 @@ import reconcile.utils.lean_terraform_client as terraform
25
24
  from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
26
25
 
27
26
  if TYPE_CHECKING:
27
+ import re
28
28
  from collections.abc import (
29
29
  Iterable,
30
30
  Iterator,
@@ -1074,28 +1074,40 @@ class AWSApi:
1074
1074
  return [rt["RouteTableId"] for rt in vpc_route_tables]
1075
1075
 
1076
1076
  @staticmethod
1077
- def _filter_amis(
1078
- images: Iterable[ImageTypeDef], regex: str
1079
- ) -> list[dict[str, Any]]:
1080
- results = []
1081
- pattern = re.compile(regex)
1082
- for i in images:
1083
- if not re.search(pattern, i["Name"]):
1084
- continue
1085
- if i["State"] != "available":
1086
- continue
1087
- item = {"image_id": i["ImageId"], "tags": i.get("Tags", [])}
1088
- results.append(item)
1077
+ def normalize_tags(tags: Iterable[TagTypeDef]) -> dict[str, str]:
1078
+ return {tag["Key"]: tag["Value"] for tag in tags}
1089
1079
 
1090
- return results
1080
+ @staticmethod
1081
+ def _filter_amis(
1082
+ images: Iterable[ImageTypeDef],
1083
+ regex: re.Pattern,
1084
+ ) -> dict[str, dict[str, str]]:
1085
+ return {
1086
+ image["ImageId"]: AWSApi.normalize_tags(image.get("Tags", []))
1087
+ for image in images
1088
+ if regex.search(image["Name"]) and image["State"] == "available"
1089
+ }
1091
1090
 
1092
1091
  def get_amis_details(
1093
1092
  self,
1094
1093
  account: Mapping[str, Any],
1095
1094
  owner_account: Mapping[str, Any],
1096
- regex: str,
1095
+ regex: re.Pattern,
1097
1096
  region: str | None = None,
1098
- ) -> list[dict[str, Any]]:
1097
+ ) -> dict[str, dict[str, str]]:
1098
+ """
1099
+ Get AMI details for an account, find AMI name matches regex and state is available.
1100
+ Return ImageId and normalized tags.
1101
+
1102
+ Args:
1103
+ account: AWS account
1104
+ owner_account: AMI owner AWS account uid
1105
+ regex: regex to filter AMI name
1106
+ region: AWS account region
1107
+
1108
+ Returns:
1109
+ dict[str, dict[str, str]]: Key is AMI ImageId, value is AMI normalized tags.
1110
+ """
1099
1111
  ec2 = self._account_ec2_client(account["name"], region_name=region)
1100
1112
  images = self.get_account_amis(ec2, owner=owner_account["uid"])
1101
1113
  return self._filter_amis(images, regex)
@@ -1175,12 +1187,31 @@ class AWSApi:
1175
1187
  client = self._account_cloudwatch_client(account_name, region_name=region_name)
1176
1188
  client.delete_log_group(logGroupName=group_name)
1177
1189
 
1178
- def create_tag(
1179
- self, account: Mapping[str, Any], resource_id: str, tag: Mapping[str, str]
1190
+ def create_tags(
1191
+ self,
1192
+ account: Mapping[str, Any],
1193
+ resource_id: str,
1194
+ tags: Mapping[str, str],
1180
1195
  ) -> None:
1196
+ """
1197
+ Create tags on EC2 resources (AMI)
1198
+
1199
+ Args:
1200
+ account: AWS account
1201
+ resource_id: AWS resource id
1202
+ tags: tags to update
1203
+
1204
+ Returns:
1205
+ None
1206
+ """
1181
1207
  ec2 = self._account_ec2_client(account["name"])
1182
- tag_type_def: TagTypeDef = {"Key": tag["Key"], "Value": tag["Value"]}
1183
- ec2.create_tags(Resources=[resource_id], Tags=[tag_type_def])
1208
+ formatted_tags: list[TagTypeDef] = [
1209
+ {"Key": k, "Value": v} for k, v in tags.items()
1210
+ ]
1211
+ ec2.create_tags(
1212
+ Resources=[resource_id],
1213
+ Tags=formatted_tags,
1214
+ )
1184
1215
 
1185
1216
  def get_alb_network_interface_ips(
1186
1217
  self, account: awsh.Account, service_name: str
@@ -6,9 +6,11 @@ from functools import cached_property
6
6
  from typing import TYPE_CHECKING, Any, TypeVar
7
7
 
8
8
  from boto3 import Session
9
+ from botocore.config import Config
9
10
  from pydantic import BaseModel
10
11
 
11
12
  import reconcile.utils.aws_api_typed.account
13
+ import reconcile.utils.aws_api_typed.cloudformation
12
14
  import reconcile.utils.aws_api_typed.dynamodb
13
15
  import reconcile.utils.aws_api_typed.iam
14
16
  import reconcile.utils.aws_api_typed.organization
@@ -17,8 +19,10 @@ import reconcile.utils.aws_api_typed.service_quotas
17
19
  import reconcile.utils.aws_api_typed.sts
18
20
  import reconcile.utils.aws_api_typed.support
19
21
  from reconcile.utils.aws_api_typed.account import AWSApiAccount
22
+ from reconcile.utils.aws_api_typed.cloudformation import AWSApiCloudFormation
20
23
  from reconcile.utils.aws_api_typed.dynamodb import AWSApiDynamoDB
21
24
  from reconcile.utils.aws_api_typed.iam import AWSApiIam
25
+ from reconcile.utils.aws_api_typed.logs import AWSApiLogs
22
26
  from reconcile.utils.aws_api_typed.organization import AWSApiOrganizations
23
27
  from reconcile.utils.aws_api_typed.s3 import AWSApiS3
24
28
  from reconcile.utils.aws_api_typed.service_quotas import AWSApiServiceQuotas
@@ -31,7 +35,9 @@ if TYPE_CHECKING:
31
35
  SubApi = TypeVar(
32
36
  "SubApi",
33
37
  AWSApiAccount,
38
+ AWSApiCloudFormation,
34
39
  AWSApiDynamoDB,
40
+ AWSApiLogs,
35
41
  AWSApiIam,
36
42
  AWSApiOrganizations,
37
43
  AWSApiS3,
@@ -40,6 +46,13 @@ SubApi = TypeVar(
40
46
  AWSApiSupport,
41
47
  )
42
48
 
49
+ DEFAULT_CONFIG = Config(
50
+ retries={
51
+ "mode": "standard",
52
+ "total_max_attempts": 10,
53
+ },
54
+ )
55
+
43
56
 
44
57
  class AWSCredentials(ABC):
45
58
  @abstractmethod
@@ -174,28 +187,34 @@ class AWSApi:
174
187
  """Return a new or cached sub api client."""
175
188
  match api_cls:
176
189
  case reconcile.utils.aws_api_typed.account.AWSApiAccount:
177
- client = self.session.client("account")
190
+ client = self.session.client("account", config=DEFAULT_CONFIG)
191
+ api = api_cls(client)
192
+ case reconcile.utils.aws_api_typed.cloudformation.AWSApiCloudFormation:
193
+ client = self.session.client("cloudformation", config=DEFAULT_CONFIG)
178
194
  api = api_cls(client)
179
195
  case reconcile.utils.aws_api_typed.dynamodb.AWSApiDynamoDB:
180
- client = self.session.client("dynamodb")
196
+ client = self.session.client("dynamodb", config=DEFAULT_CONFIG)
181
197
  api = api_cls(client)
182
198
  case reconcile.utils.aws_api_typed.iam.AWSApiIam:
183
- client = self.session.client("iam")
199
+ client = self.session.client("iam", config=DEFAULT_CONFIG)
200
+ api = api_cls(client)
201
+ case reconcile.utils.aws_api_typed.logs.AWSApiLogs:
202
+ client = self.session.client("logs", config=DEFAULT_CONFIG)
184
203
  api = api_cls(client)
185
204
  case reconcile.utils.aws_api_typed.organization.AWSApiOrganizations:
186
- client = self.session.client("organizations")
205
+ client = self.session.client("organizations", config=DEFAULT_CONFIG)
187
206
  api = api_cls(client)
188
207
  case reconcile.utils.aws_api_typed.s3.AWSApiS3:
189
- client = self.session.client("s3")
208
+ client = self.session.client("s3", config=DEFAULT_CONFIG)
190
209
  api = api_cls(client)
191
210
  case reconcile.utils.aws_api_typed.service_quotas.AWSApiServiceQuotas:
192
- client = self.session.client("service-quotas")
211
+ client = self.session.client("service-quotas", config=DEFAULT_CONFIG)
193
212
  api = api_cls(client)
194
213
  case reconcile.utils.aws_api_typed.sts.AWSApiSts:
195
- client = self.session.client("sts")
214
+ client = self.session.client("sts", config=DEFAULT_CONFIG)
196
215
  api = api_cls(client)
197
216
  case reconcile.utils.aws_api_typed.support.AWSApiSupport:
198
- client = self.session.client("support")
217
+ client = self.session.client("support", config=DEFAULT_CONFIG)
199
218
  api = api_cls(client)
200
219
  case _:
201
220
  raise ValueError(f"Unknown API class: {api_cls}")
@@ -205,9 +224,14 @@ class AWSApi:
205
224
 
206
225
  @cached_property
207
226
  def account(self) -> AWSApiAccount:
208
- """Return an AWS Acount Api client"""
227
+ """Return an AWS Account Api client"""
209
228
  return self._init_sub_api(AWSApiAccount)
210
229
 
230
+ @cached_property
231
+ def cloudformation(self) -> AWSApiCloudFormation:
232
+ """Return an AWS CloudFormation Api client"""
233
+ return self._init_sub_api(AWSApiCloudFormation)
234
+
211
235
  @cached_property
212
236
  def dynamodb(self) -> AWSApiDynamoDB:
213
237
  """Return an AWS DynamoDB Api client"""
@@ -218,6 +242,11 @@ class AWSApi:
218
242
  """Return an AWS IAM Api client."""
219
243
  return self._init_sub_api(AWSApiIam)
220
244
 
245
+ @cached_property
246
+ def logs(self) -> AWSApiLogs:
247
+ """Return an AWS Logs Api client."""
248
+ return self._init_sub_api(AWSApiLogs)
249
+
221
250
  @cached_property
222
251
  def organizations(self) -> AWSApiOrganizations:
223
252
  """Return an AWS Organizations Api client."""
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from reconcile.utils.json import json_dumps
6
+
7
+ if TYPE_CHECKING:
8
+ from mypy_boto3_cloudformation import CloudFormationClient
9
+ from mypy_boto3_cloudformation.type_defs import (
10
+ ParameterTypeDef,
11
+ StackTypeDef,
12
+ TagTypeDef,
13
+ )
14
+
15
+
16
+ class AWSApiCloudFormation:
17
+ def __init__(self, client: CloudFormationClient) -> None:
18
+ self.client = client
19
+
20
+ def create_stack(
21
+ self,
22
+ stack_name: str,
23
+ change_set_name: str,
24
+ template_body: str,
25
+ parameters: dict[str, str] | None = None,
26
+ tags: dict[str, str] | None = None,
27
+ ) -> str:
28
+ """
29
+ Create a CloudFormation stack using a change set with import existing resources.
30
+ This method creates a change set of type "CREATE" with the provided template and parameters,
31
+ waits for the change set to be created, executes it, and then waits for the stack creation to complete.
32
+ It returns the StackId of the created stack.
33
+
34
+ Args:
35
+ stack_name (str): The name of the stack to create.
36
+ change_set_name (str): The name of the change set to create.
37
+ template_body (str): The CloudFormation template body as a string.
38
+ parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
39
+ the stack. Defaults to None.
40
+ tags (dict[str, str] | None): A dictionary of tag key-value pairs to
41
+ associate with the stack. Defaults to None.
42
+
43
+ Returns:
44
+ str: The StackId of the created stack.
45
+ """
46
+ response = self.client.create_change_set(
47
+ StackName=stack_name,
48
+ ChangeSetName=change_set_name,
49
+ TemplateBody=template_body,
50
+ ChangeSetType="CREATE",
51
+ Parameters=self._build_parameters(parameters or {}),
52
+ Tags=self._build_tags(tags or {}),
53
+ ImportExistingResources=True,
54
+ )
55
+ change_set_arn = response["Id"]
56
+ self.client.get_waiter("change_set_create_complete").wait(
57
+ ChangeSetName=change_set_arn
58
+ )
59
+ self.client.execute_change_set(ChangeSetName=change_set_arn)
60
+ self.client.get_waiter("stack_create_complete").wait(StackName=stack_name)
61
+ return response["StackId"]
62
+
63
+ def update_stack(
64
+ self,
65
+ stack_name: str,
66
+ template_body: str,
67
+ parameters: dict[str, str] | None = None,
68
+ tags: dict[str, str] | None = None,
69
+ ) -> str:
70
+ """
71
+ Update a CloudFormation stack with the provided template and parameters.
72
+ This method updates the specified stack, waits for the update to complete,
73
+ and returns the StackId of the updated stack.
74
+
75
+ Args:
76
+ stack_name (str): The name of the stack to update.
77
+ template_body (str): The CloudFormation template body as a string.
78
+ parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
79
+ the stack. Defaults to None.
80
+ tags (dict[str, str] | None): A dictionary of tag key-value pairs to
81
+ associate with the stack. Defaults to None.
82
+
83
+ Returns:
84
+ str: The StackId of the updated stack.
85
+ """
86
+ response = self.client.update_stack(
87
+ StackName=stack_name,
88
+ TemplateBody=template_body,
89
+ Parameters=self._build_parameters(parameters or {}),
90
+ Tags=self._build_tags(tags or {}),
91
+ )
92
+ self.client.get_waiter("stack_update_complete").wait(StackName=stack_name)
93
+ return response["StackId"]
94
+
95
+ def get_stack(self, stack_name: str) -> StackTypeDef | None:
96
+ """
97
+ Retrieve information about a CloudFormation stack by its name.
98
+ If the stack exists, it returns the stack details as a dictionary.
99
+ If the stack does not exist, it returns None.
100
+
101
+ Args:
102
+ stack_name (str): The name of the stack to retrieve.
103
+
104
+ Returns:
105
+ StackTypeDef | None: The stack details if found, otherwise None.
106
+ """
107
+ try:
108
+ response = self.client.describe_stacks(StackName=stack_name)
109
+ return response["Stacks"][0]
110
+ except self.client.exceptions.ClientError as e:
111
+ if e.response["Error"]["Code"] == "ValidationError":
112
+ return None
113
+ raise
114
+
115
+ def get_template_body(self, stack_name: str) -> str:
116
+ """
117
+ Retrieve the CloudFormation template body for a specified stack.
118
+
119
+ Args:
120
+ stack_name (str): The name of the stack whose template is to be retrieved.
121
+
122
+ Returns:
123
+ str: The CloudFormation template body as a string.
124
+ """
125
+ response = self.client.get_template(StackName=stack_name)
126
+ # TemplateBody is str when using yaml
127
+ if isinstance(response["TemplateBody"], str):
128
+ return response["TemplateBody"]
129
+ return json_dumps(response["TemplateBody"])
130
+
131
+ @staticmethod
132
+ def _build_parameters(parameters: dict[str, str]) -> list[ParameterTypeDef]:
133
+ return [
134
+ {
135
+ "ParameterKey": key,
136
+ "ParameterValue": value,
137
+ }
138
+ for key, value in sorted(parameters.items())
139
+ ]
140
+
141
+ @staticmethod
142
+ def _build_tags(tags: dict[str, str]) -> list[TagTypeDef]:
143
+ return [
144
+ {
145
+ "Key": key,
146
+ "Value": value,
147
+ }
148
+ for key, value in sorted(tags.items())
149
+ ]
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from collections.abc import Iterable, Iterator
7
+
8
+ from mypy_boto3_logs import CloudWatchLogsClient
9
+ from mypy_boto3_logs.type_defs import LogGroupTypeDef
10
+
11
+
12
+ class AWSApiLogs:
13
+ def __init__(self, client: CloudWatchLogsClient) -> None:
14
+ self.client = client
15
+
16
+ def get_log_groups(self) -> Iterator[LogGroupTypeDef]:
17
+ paginator = self.client.get_paginator("describe_log_groups")
18
+ for page in paginator.paginate():
19
+ yield from page["logGroups"]
20
+
21
+ def delete_log_group(self, log_group_name: str) -> None:
22
+ self.client.delete_log_group(logGroupName=log_group_name)
23
+
24
+ def put_retention_policy(
25
+ self,
26
+ log_group_name: str,
27
+ retention_in_days: int,
28
+ ) -> None:
29
+ self.client.put_retention_policy(
30
+ logGroupName=log_group_name,
31
+ retentionInDays=retention_in_days,
32
+ )
33
+
34
+ def get_tags(self, arn: str) -> dict[str, str]:
35
+ tags = self.client.list_tags_for_resource(
36
+ resourceArn=self._normalize_log_group_arn(arn),
37
+ )
38
+ return tags.get("tags") or {}
39
+
40
+ def set_tags(
41
+ self,
42
+ arn: str,
43
+ tags: dict[str, str],
44
+ ) -> None:
45
+ self.client.tag_resource(
46
+ resourceArn=self._normalize_log_group_arn(arn),
47
+ tags=tags,
48
+ )
49
+
50
+ def delete_tags(
51
+ self,
52
+ arn: str,
53
+ tag_keys: Iterable[str],
54
+ ) -> None:
55
+ self.client.untag_resource(
56
+ resourceArn=self._normalize_log_group_arn(arn),
57
+ tagKeys=list(tag_keys),
58
+ )
59
+
60
+ @staticmethod
61
+ def _normalize_log_group_arn(arn: str) -> str:
62
+ """
63
+ Normalize a log group ARN by removing any trailing ":*".
64
+
65
+ DescribeLogGroups response arn has additional :* at the end.
66
+
67
+ Args:
68
+ arn: The ARN of the log group.
69
+
70
+ Returns:
71
+ The normalized ARN without the trailing ":*".
72
+ """
73
+ return arn.rstrip(":*")
@@ -52,9 +52,11 @@ class AwsOrganizationOU(BaseModel):
52
52
  class AWSAccountStatus(BaseModel):
53
53
  id: str = Field(..., alias="Id")
54
54
  name: str = Field(..., alias="AccountName")
55
- uid: str | None = Field(alias="AccountId")
55
+ uid: str | None = Field(None, alias="AccountId")
56
56
  state: str = Field(..., alias="State")
57
- failure_reason: CreateAccountFailureReasonType | None = Field(alias="FailureReason")
57
+ failure_reason: CreateAccountFailureReasonType | None = Field(
58
+ None, alias="FailureReason"
59
+ )
58
60
 
59
61
 
60
62
  class AWSAccount(BaseModel):
reconcile/utils/binary.py CHANGED
@@ -38,10 +38,7 @@ def binary_version(
38
38
  def deco_binary_version(f: Callable) -> Callable:
39
39
  @wraps(f)
40
40
  def f_binary_version(*args: Any, **kwargs: Any) -> None:
41
- regex = re.compile(search_regex)
42
-
43
- cmd = [binary]
44
- cmd.extend(version_args)
41
+ cmd = [binary, *version_args]
45
42
  try:
46
43
  result = subprocess.run(cmd, capture_output=True, check=True)
47
44
  except subprocess.CalledProcessError as e:
@@ -50,15 +47,13 @@ def binary_version(
50
47
  )
51
48
  raise Exception(msg) from e
52
49
 
53
- found = False
54
- match = None
55
- for line in result.stdout.splitlines():
56
- match = regex.search(line.decode("utf-8"))
57
- if match is not None:
58
- found = True
59
- break
50
+ match = re.search(
51
+ search_regex,
52
+ result.stdout.decode("utf-8"),
53
+ re.MULTILINE,
54
+ )
60
55
 
61
- if not found or not match:
56
+ if match is None:
62
57
  raise Exception(
63
58
  f"Could not find version for binary '{binary}' via regex "
64
59
  f"for binary version check: "
@@ -0,0 +1,67 @@
1
+ from datetime import UTC, datetime
2
+
3
+ ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
4
+ ISO8601_MICRO = "%Y-%m-%dT%H:%M:%S.%fZ"
5
+
6
+
7
+ def utc_now() -> datetime:
8
+ """
9
+ Get the current UTC datetime.
10
+
11
+ Returns:
12
+ A datetime object representing the current time in UTC.
13
+ """
14
+ return datetime.now(tz=UTC)
15
+
16
+
17
+ def ensure_utc(dt: datetime) -> datetime:
18
+ """
19
+ Ensure the provided datetime is in UTC timezone.
20
+
21
+ Args:
22
+ dt: A datetime object.
23
+
24
+ Returns:
25
+ A datetime object in UTC timezone.
26
+ """
27
+ if dt.tzinfo is None:
28
+ return dt.replace(tzinfo=UTC)
29
+ return dt.astimezone(UTC)
30
+
31
+
32
+ def to_utc_seconds_iso_format(dt: datetime) -> str:
33
+ """
34
+ Convert a datetime object to ISO 8601 format YYYY-MM-DDTHH:MM:SSZ.
35
+
36
+ Args:
37
+ dt: A datetime object.
38
+
39
+ Returns:
40
+ A string representing the datetime in ISO 8601 format.
41
+ """
42
+ return ensure_utc(dt).strftime(ISO8601)
43
+
44
+
45
+ def to_utc_microseconds_iso_format(dt: datetime) -> str:
46
+ """
47
+ Convert a datetime object to ISO 8601 format with microseconds YYYY-MM-DDTHH:MM:SS.mmmmmmZ.
48
+
49
+ Args:
50
+ dt: A datetime object.
51
+
52
+ Returns:
53
+ A string representing the datetime in ISO 8601 format with microseconds.
54
+ """
55
+ return ensure_utc(dt).strftime(ISO8601_MICRO)
56
+
57
+
58
+ def from_utc_iso_format(dt_str: str) -> datetime:
59
+ """
60
+ Parse a datetime string in ISO 8601 format to a datetime object.
61
+
62
+ Args:
63
+ dt_str: A string representing the datetime in ISO 8601 format.
64
+ Returns:
65
+ A datetime object in UTC timezone.
66
+ """
67
+ return ensure_utc(datetime.fromisoformat(dt_str))
@@ -26,7 +26,7 @@ class Snitch(BaseModel):
26
26
  interval: str
27
27
  alert_type: str
28
28
  alert_email: list[str]
29
- vault_data: str | None
29
+ vault_data: str | None = None
30
30
 
31
31
  def needs_vault_update(self) -> bool:
32
32
  return self.vault_data is not None and self.check_in_url != self.vault_data
reconcile/utils/differ.py CHANGED
@@ -7,7 +7,6 @@ from collections.abc import (
7
7
  from dataclasses import dataclass
8
8
  from typing import (
9
9
  Any,
10
- Generic,
11
10
  TypeVar,
12
11
  )
13
12
 
@@ -18,13 +17,13 @@ Key = TypeVar("Key")
18
17
 
19
18
 
20
19
  @dataclass(frozen=True, eq=True)
21
- class DiffPair(Generic[Current, Desired]):
20
+ class DiffPair[Current, Desired]:
22
21
  current: Current
23
22
  desired: Desired
24
23
 
25
24
 
26
25
  @dataclass(frozen=True, eq=True)
27
- class DiffResult(Generic[Current, Desired, Key]):
26
+ class DiffResult[Current, Desired, Key]:
28
27
  add: dict[Key, Desired]
29
28
  delete: dict[Key, Current]
30
29
  change: dict[Key, DiffPair[Current, Desired]]