qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,7 @@
1
1
  from collections.abc import Generator
2
- from datetime import (
3
- datetime,
4
- timedelta,
5
- )
2
+ from datetime import timedelta
6
3
 
4
+ from reconcile.utils.datetime_util import utc_now
7
5
  from reconcile.utils.ocm.base import (
8
6
  OCMClusterServiceLog,
9
7
  OCMClusterServiceLogCreateModel,
@@ -47,7 +45,7 @@ def create_service_log(
47
45
  .eq("severity", service_log.severity.value)
48
46
  .eq("summary", service_log.summary)
49
47
  .eq("description", service_log.description)
50
- .after("created_at", datetime.utcnow() - dedup_interval),
48
+ .after("created_at", utc_now() - dedup_interval),
51
49
  ),
52
50
  None,
53
51
  )
@@ -57,6 +55,6 @@ def create_service_log(
57
55
  return OCMClusterServiceLog(
58
56
  **ocm_api.post(
59
57
  api_path=CLUSTER_SERVICE_LOGS_CREATE_ENDPOINT,
60
- data=service_log.dict(by_alias=True),
58
+ data=service_log.model_dump(by_alias=True),
61
59
  )
62
60
  )
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
- from pydantic import Field
4
- from pydantic.fields import ModelField
3
+ from pydantic import WithJsonSchema
4
+ from pydantic.fields import FieldInfo
5
5
 
6
6
  from reconcile.utils.ocm.base import (
7
7
  LabelContainer,
@@ -22,11 +22,11 @@ def sre_capability_label_key(
22
22
  return f"sre-capabilities.{sre_capability}.{config_atom}"
23
23
 
24
24
 
25
- def labelset_groupfield(group_prefix: str) -> Any:
25
+ def labelset_groupfield(group_prefix: str) -> WithJsonSchema:
26
26
  """
27
27
  Helper function to build the FieldMeta for a labelset field that groups labels.
28
28
  """
29
- return Field(group_by_prefix=group_prefix)
29
+ return WithJsonSchema({"group_by_prefix": group_prefix})
30
30
 
31
31
 
32
32
  def build_labelset(
@@ -36,16 +36,23 @@ def build_labelset(
36
36
  Instantiates a dataclass from a set of labels.
37
37
  """
38
38
  raw_data = {
39
- field.alias: _labelset_field_value(labels, field)
40
- for field in dataclass.__fields__.values()
39
+ field.alias or name: _labelset_field_value(labels, name, field)
40
+ for name, field in dataclass.model_fields.items()
41
41
  }
42
42
  return dataclass(**raw_data)
43
43
 
44
44
 
45
- def _labelset_field_value(labels: LabelContainer, field: ModelField) -> Any | None:
46
- key_prefix = field.field_info.extra.get("group_by_prefix")
47
- if key_prefix:
48
- return build_container_for_prefix(
49
- labels, key_prefix, strip_key_prefix=True
50
- ).get_values_dict()
51
- return labels.get_label_value(field.alias)
45
+ def _labelset_field_value(
46
+ labels: LabelContainer, name: str, field: FieldInfo
47
+ ) -> Any | None:
48
+ schema = next((m for m in field.metadata if isinstance(m, WithJsonSchema)), None)
49
+ if (
50
+ schema is None
51
+ or not schema.json_schema
52
+ or "group_by_prefix" not in schema.json_schema
53
+ ):
54
+ return labels.get_label_value(field.alias or name)
55
+
56
+ return build_container_for_prefix(
57
+ labels, schema.json_schema["group_by_prefix"], strip_key_prefix=True
58
+ ).get_values_dict()
@@ -49,7 +49,7 @@ class OCMBaseClient:
49
49
  self._init_request_headers()
50
50
 
51
51
  @retry()
52
- def _init_access_token(self):
52
+ def _init_access_token(self) -> None:
53
53
  data = {
54
54
  "grant_type": "client_credentials",
55
55
  "client_id": self._access_token_client_id,
@@ -61,7 +61,7 @@ class OCMBaseClient:
61
61
  r.raise_for_status()
62
62
  self._access_token = r.json().get("access_token")
63
63
 
64
- def _init_request_headers(self):
64
+ def _init_request_headers(self) -> None:
65
65
  self._session.headers.update({
66
66
  "Authorization": f"Bearer {self._access_token}",
67
67
  "accept": "application/json",
@@ -130,7 +130,7 @@ class OCMBaseClient:
130
130
  api_path: str,
131
131
  data: Mapping[str, Any],
132
132
  params: Mapping[str, str] | None = None,
133
- ):
133
+ ) -> None:
134
134
  ocm_request.labels(verb="PATCH", client_id=self._access_token_client_id).inc()
135
135
  r = self._session.patch(
136
136
  f"{self._url}{api_path}",
@@ -144,7 +144,7 @@ class OCMBaseClient:
144
144
  logging.error(r.text)
145
145
  raise e
146
146
 
147
- def delete(self, api_path: str):
147
+ def delete(self, api_path: str) -> None:
148
148
  ocm_request.labels(verb="DELETE", client_id=self._access_token_client_id).inc()
149
149
  r = self._session.delete(f"{self._url}{api_path}", timeout=REQUEST_TIMEOUT_SEC)
150
150
  try:
@@ -1,20 +1,26 @@
1
1
  # ruff: noqa: SIM114
2
+ from __future__ import annotations
3
+
2
4
  import base64
3
5
  import contextlib
4
6
  import copy
5
- import datetime
6
7
  import hashlib
7
- import json
8
+ import logging
8
9
  import re
9
- from collections.abc import Mapping
10
10
  from threading import Lock
11
+ from typing import TYPE_CHECKING, Any
11
12
 
12
13
  import semver
13
14
  from pydantic import BaseModel
14
15
 
15
16
  from reconcile.external_resources.meta import SECRET_UPDATED_AT
17
+ from reconcile.utils.datetime_util import to_utc_seconds_iso_format, utc_now
18
+ from reconcile.utils.json import json_dumps
16
19
  from reconcile.utils.metrics import GaugeMetric
17
20
 
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Iterator, Mapping
23
+
18
24
  SECRET_MAX_KEY_LENGTH = 253
19
25
 
20
26
 
@@ -27,7 +33,7 @@ class ResourceNotManagedError(Exception):
27
33
 
28
34
 
29
35
  class ConstructResourceError(Exception):
30
- def __init__(self, msg):
36
+ def __init__(self, msg: str) -> None:
31
37
  super().__init__("error constructing openshift resource: " + str(msg))
32
38
 
33
39
 
@@ -73,13 +79,13 @@ QONTRACT_ANNOTATIONS = {
73
79
  class OpenshiftResource:
74
80
  def __init__(
75
81
  self,
76
- body,
77
- integration,
78
- integration_version,
79
- error_details="",
80
- caller_name=None,
81
- validate_k8s_object=True,
82
- ):
82
+ body: dict[str, Any],
83
+ integration: str,
84
+ integration_version: str,
85
+ error_details: str = "",
86
+ caller_name: str | None = None,
87
+ validate_k8s_object: bool = True,
88
+ ) -> None:
83
89
  self.body = body
84
90
  self.integration = integration
85
91
  self.integration_version = integration_version
@@ -88,10 +94,12 @@ class OpenshiftResource:
88
94
  if validate_k8s_object:
89
95
  self.verify_valid_k8s_object()
90
96
 
91
- def __eq__(self, other):
97
+ def __eq__(self, other: object) -> bool:
98
+ if not isinstance(other, OpenshiftResource):
99
+ return False
92
100
  return self.obj_intersect_equal(self.body, other.body)
93
101
 
94
- def obj_intersect_equal(self, obj1, obj2, depth=0):
102
+ def obj_intersect_equal(self, obj1: Any, obj2: Any, depth: int = 0) -> bool:
95
103
  # obj1 == d_item
96
104
  # obj2 == c_item
97
105
  if obj1.__class__ != obj2.__class__:
@@ -163,7 +171,7 @@ class OpenshiftResource:
163
171
  return True
164
172
 
165
173
  @staticmethod
166
- def ignorable_field(val):
174
+ def ignorable_field(val: str) -> bool:
167
175
  ignorable_fields = [
168
176
  "kubectl.kubernetes.io/last-applied-configuration",
169
177
  "creationTimestamp",
@@ -176,14 +184,14 @@ class OpenshiftResource:
176
184
  return val in ignorable_fields
177
185
 
178
186
  @staticmethod
179
- def ignorable_key_value_pair(key, val):
187
+ def ignorable_key_value_pair(key: str, val: Any) -> bool:
180
188
  ignorable_key_value_pair = {"annotations": None, "divisor": "0"}
181
189
  return bool(
182
190
  key in ignorable_key_value_pair and ignorable_key_value_pair[key] == val
183
191
  )
184
192
 
185
193
  @staticmethod
186
- def cpu_equal(val1, val2):
194
+ def cpu_equal(val1: Any, val2: Any) -> bool:
187
195
  # normalize both to string
188
196
  with contextlib.suppress(Exception):
189
197
  val1 = f"{int(float(val1) * 1000)}m"
@@ -192,7 +200,7 @@ class OpenshiftResource:
192
200
  return val1 == val2
193
201
 
194
202
  @staticmethod
195
- def api_version_mutation(val1, val2):
203
+ def api_version_mutation(val1: str, val2: str) -> bool:
196
204
  # required temporarily, pending response on
197
205
  # https://redhat.service-now.com/surl.do?n=INC1224482
198
206
  if val1 == "apps/v1" and val2 == "extensions/v1beta1":
@@ -204,7 +212,7 @@ class OpenshiftResource:
204
212
  return val1 == val2
205
213
 
206
214
  @property
207
- def name(self):
215
+ def name(self) -> str:
208
216
  # PipelineRun name can be empty when creating
209
217
  if self.kind == "PipelineRun" and "name" not in self.body["metadata"]:
210
218
  return self.body["metadata"]["generateName"][:-1]
@@ -212,19 +220,19 @@ class OpenshiftResource:
212
220
  return self.body["metadata"]["name"]
213
221
 
214
222
  @property
215
- def kind(self):
223
+ def kind(self) -> str:
216
224
  return self.body["kind"]
217
225
 
218
226
  @property
219
- def annotations(self):
227
+ def annotations(self) -> dict[str, str]:
220
228
  return self.body["metadata"].get("annotations", {})
221
229
 
222
230
  @property
223
- def kind_and_group(self):
231
+ def kind_and_group(self) -> str:
224
232
  return fully_qualified_kind(self.kind, self.body["apiVersion"])
225
233
 
226
234
  @property
227
- def caller(self):
235
+ def caller(self) -> str | None:
228
236
  try:
229
237
  return (
230
238
  self.caller_name
@@ -233,7 +241,7 @@ class OpenshiftResource:
233
241
  except KeyError:
234
242
  return None
235
243
 
236
- def verify_valid_k8s_object(self):
244
+ def verify_valid_k8s_object(self) -> None:
237
245
  try:
238
246
  assert self.name
239
247
  assert self.kind
@@ -291,7 +299,7 @@ class OpenshiftResource:
291
299
  pass
292
300
 
293
301
  @staticmethod
294
- def is_controller_managed_label(kind, label) -> bool:
302
+ def is_controller_managed_label(kind: str, label: str) -> bool:
295
303
  for il in CONTROLLER_MANAGED_LABELS.get(kind, []):
296
304
  if isinstance(il, str) and il == label:
297
305
  return True
@@ -299,7 +307,7 @@ class OpenshiftResource:
299
307
  return True
300
308
  return False
301
309
 
302
- def has_qontract_annotations(self):
310
+ def has_qontract_annotations(self) -> bool:
303
311
  try:
304
312
  annotations = self.body["metadata"]["annotations"]
305
313
 
@@ -322,10 +330,10 @@ class OpenshiftResource:
322
330
 
323
331
  return True
324
332
 
325
- def has_owner_reference(self):
333
+ def has_owner_reference(self) -> bool:
326
334
  return bool(self.body["metadata"].get("ownerReferences", []))
327
335
 
328
- def has_valid_sha256sum(self):
336
+ def has_valid_sha256sum(self) -> bool:
329
337
  try:
330
338
  current_sha256sum = self.body["metadata"]["annotations"][
331
339
  "qontract.sha256sum"
@@ -334,7 +342,7 @@ class OpenshiftResource:
334
342
  except KeyError:
335
343
  return False
336
344
 
337
- def annotate(self, canonicalize=True):
345
+ def annotate(self, canonicalize: bool = True) -> OpenshiftResource:
338
346
  """
339
347
  Creates a OpenshiftResource with the qontract annotations, and removes
340
348
  unneeded Openshift fields.
@@ -361,24 +369,24 @@ class OpenshiftResource:
361
369
  annotations[QONTRACT_ANNOTATION_INTEGRATION] = self.integration
362
370
  annotations[QONTRACT_ANNOTATION_INTEGRATION_VERSION] = self.integration_version
363
371
  annotations[QONTRACT_ANNOTATION_SHA256SUM] = sha256sum
364
- now = datetime.datetime.utcnow().replace(microsecond=0).isoformat()
365
- annotations[QONTRACT_ANNOTATION_UPDATE] = now
372
+ now = utc_now()
373
+ annotations[QONTRACT_ANNOTATION_UPDATE] = to_utc_seconds_iso_format(now)
366
374
  if self.caller_name:
367
375
  annotations[QONTRACT_ANNOTATION_CALLER_NAME] = self.caller_name
368
376
 
369
377
  return OpenshiftResource(body, self.integration, self.integration_version)
370
378
 
371
- def sha256sum(self):
379
+ def sha256sum(self) -> str:
372
380
  body = self.annotate().body
373
381
 
374
382
  annotations = body["metadata"]["annotations"]
375
383
  return annotations["qontract.sha256sum"]
376
384
 
377
- def to_json(self):
385
+ def to_json(self) -> str:
378
386
  return self.serialize(self.body)
379
387
 
380
388
  @staticmethod
381
- def canonicalize(body):
389
+ def canonicalize(body: dict[str, Any]) -> dict[str, Any]:
382
390
  body = copy.deepcopy(body)
383
391
 
384
392
  # create annotations if not present
@@ -522,11 +530,11 @@ class OpenshiftResource:
522
530
  return body
523
531
 
524
532
  @staticmethod
525
- def serialize(body):
526
- return json.dumps(body, sort_keys=True)
533
+ def serialize(body: dict[str, Any]) -> str:
534
+ return json_dumps(body)
527
535
 
528
536
  @staticmethod
529
- def calculate_sha256sum(body):
537
+ def calculate_sha256sum(body: str) -> str:
530
538
  m = hashlib.sha256()
531
539
  m.update(body.encode("utf-8"))
532
540
  return m.hexdigest()
@@ -559,19 +567,19 @@ class OpenshiftResourceInventoryGauge(OpenshiftResourceBaseMetric, GaugeMetric):
559
567
 
560
568
 
561
569
  class ResourceInventory:
562
- def __init__(self):
563
- self._clusters = {}
570
+ def __init__(self) -> None:
571
+ self._clusters: dict[str, dict[str, dict[str, dict[str, Any]]]] = {}
564
572
  self._error_registered = False
565
- self._error_registered_clusters = {}
573
+ self._error_registered_clusters: dict[str, bool] = {}
566
574
  self._lock = Lock()
567
575
 
568
576
  def initialize_resource_type(
569
577
  self,
570
- cluster,
571
- namespace,
572
- resource_type,
578
+ cluster: str,
579
+ namespace: str,
580
+ resource_type: str,
573
581
  managed_names: list[str] | None = None,
574
- ):
582
+ ) -> None:
575
583
  self._clusters.setdefault(cluster, {})
576
584
  self._clusters[cluster].setdefault(namespace, {})
577
585
  self._clusters[cluster][namespace].setdefault(
@@ -594,6 +602,10 @@ class ResourceInventory:
594
602
  resource: OpenshiftResource,
595
603
  privileged: bool = False,
596
604
  ) -> None:
605
+ if cluster not in self._clusters:
606
+ logging.error(f"Cluster {cluster} not initialized in ResourceInventory")
607
+ return
608
+
597
609
  if resource.kind_and_group in self._clusters[cluster][namespace]:
598
610
  kind = resource.kind_and_group
599
611
  else:
@@ -608,8 +620,14 @@ class ResourceInventory:
608
620
  )
609
621
 
610
622
  def add_desired(
611
- self, cluster, namespace, resource_type, name, value, privileged=False
612
- ):
623
+ self,
624
+ cluster: str,
625
+ namespace: str,
626
+ resource_type: str,
627
+ name: str,
628
+ value: OpenshiftResource,
629
+ privileged: bool = False,
630
+ ) -> None:
613
631
  # privileged permissions to apply resources to clusters are managed on
614
632
  # a per-namespace level in qontract-schema namespace files, but are
615
633
  # tracked on a per-resource level in ResourceInventory and the
@@ -633,41 +651,54 @@ class ResourceInventory:
633
651
  ]
634
652
  admin_token_usage[name] = privileged
635
653
 
636
- def get_desired(self, cluster, namespace, resource_type, name):
654
+ def get_desired(
655
+ self, cluster: str, namespace: str, resource_type: str, name: str
656
+ ) -> OpenshiftResource | None:
637
657
  try:
638
658
  return self._clusters[cluster][namespace][resource_type]["desired"][name]
639
659
  except KeyError:
640
660
  return None
641
661
 
642
- def get_desired_by_type(self, cluster, namespace, resource_type):
662
+ def get_desired_by_type(
663
+ self, cluster: str, namespace: str, resource_type: str
664
+ ) -> dict[str, OpenshiftResource] | None:
643
665
  try:
644
666
  return self._clusters[cluster][namespace][resource_type]["desired"]
645
667
  except KeyError:
646
668
  return None
647
669
 
648
- def get_current(self, cluster, namespace, resource_type, name):
670
+ def get_current(
671
+ self, cluster: str, namespace: str, resource_type: str, name: str
672
+ ) -> OpenshiftResource | None:
649
673
  try:
650
674
  return self._clusters[cluster][namespace][resource_type]["current"][name]
651
675
  except KeyError:
652
676
  return None
653
677
 
654
- def add_current(self, cluster, namespace, resource_type, name, value):
678
+ def add_current(
679
+ self,
680
+ cluster: str,
681
+ namespace: str,
682
+ resource_type: str,
683
+ name: str,
684
+ value: OpenshiftResource,
685
+ ) -> None:
655
686
  with self._lock:
656
687
  current = self._clusters[cluster][namespace][resource_type]["current"]
657
688
  current[name] = value
658
689
 
659
- def __iter__(self):
690
+ def __iter__(self) -> Iterator[tuple[str, str, str, dict[str, Any]]]:
660
691
  for cluster_name, cluster in self._clusters.items():
661
692
  for namespace_name, namespace in cluster.items():
662
693
  for resource_type, resource in namespace.items():
663
694
  yield (cluster_name, namespace_name, resource_type, resource)
664
695
 
665
- def register_error(self, cluster=None):
696
+ def register_error(self, cluster: str | None = None) -> None:
666
697
  self._error_registered = True
667
698
  if cluster is not None:
668
699
  self._error_registered_clusters[cluster] = True
669
700
 
670
- def has_error_registered(self, cluster=None):
701
+ def has_error_registered(self, cluster: str | None = None) -> bool:
671
702
  if cluster is not None:
672
703
  return self._error_registered_clusters.get(cluster, False)
673
704
  return self._error_registered
@@ -1,12 +1,12 @@
1
1
  from OpenSSL import crypto
2
2
 
3
3
 
4
- def certificate_matches_host(certificate, host):
4
+ def certificate_matches_host(certificate: bytes, host: str) -> bool:
5
5
  common_name = get_certificate_common_name(certificate)
6
6
  return host.endswith(common_name.replace("*.", ""))
7
7
 
8
8
 
9
- def get_certificate_common_name(certificate):
9
+ def get_certificate_common_name(certificate: bytes) -> str:
10
10
  cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
11
11
  subject = cert.get_subject()
12
12
  return subject.CN
reconcile/utils/output.py CHANGED
@@ -1,10 +1,11 @@
1
- import json
2
1
  import re
3
2
  from collections.abc import Iterable, Mapping
4
3
 
5
4
  import yaml
6
5
  from tabulate import tabulate
7
6
 
7
+ from reconcile.utils.json import json_dumps
8
+
8
9
 
9
10
  def print_output(
10
11
  options: Mapping[str, str | bool],
@@ -30,7 +31,7 @@ def print_output(
30
31
  )
31
32
  print(formatted_content)
32
33
  elif output == "json":
33
- formatted_content = json.dumps(content)
34
+ formatted_content = json_dumps(content)
34
35
  print(formatted_content)
35
36
  elif output == "yaml":
36
37
  formatted_content = yaml.dump(content)
@@ -3,8 +3,7 @@ from collections.abc import (
3
3
  Callable,
4
4
  Iterable,
5
5
  )
6
- from datetime import datetime as dt
7
- from datetime import timedelta
6
+ from datetime import datetime, timedelta
8
7
  from typing import (
9
8
  Protocol,
10
9
  )
@@ -14,6 +13,7 @@ import requests
14
13
  from pydantic import BaseModel
15
14
  from sretoolbox.utils import retry
16
15
 
16
+ from reconcile.utils.datetime_util import utc_now
17
17
  from reconcile.utils.secret_reader import (
18
18
  HasSecret,
19
19
  SecretReader,
@@ -52,10 +52,13 @@ class PagerDutyTarget(Protocol):
52
52
  which must be implemented by a class to be compatible."""
53
53
 
54
54
  name: str
55
- instance: PagerDutyInstance
56
55
  escalation_policy_id: str | None
57
56
  schedule_id: str | None
58
57
 
58
+ @property
59
+ def instance(self) -> PagerDutyInstance:
60
+ pass
61
+
59
62
 
60
63
  class PagerDutyConfig(BaseModel):
61
64
  """PagerDuty Config."""
@@ -80,7 +83,7 @@ class PagerDutyApi:
80
83
  def get_pagerduty_users(
81
84
  self, resource_type: str, resource_id: str
82
85
  ) -> list[pypd.User]:
83
- now = dt.utcnow()
86
+ now = utc_now()
84
87
 
85
88
  try:
86
89
  if resource_type == "schedule":
@@ -103,7 +106,7 @@ class PagerDutyApi:
103
106
  self.users.append(user)
104
107
  return user.email.split("@")[0]
105
108
 
106
- def get_schedule_users(self, schedule_id: str, now: dt) -> list[pypd.User]:
109
+ def get_schedule_users(self, schedule_id: str, now: datetime) -> list[pypd.User]:
107
110
  until = now + timedelta(seconds=60)
108
111
  s = pypd.Schedule.fetch(id=schedule_id, since=now, until=until, time_zone="UTC")
109
112
  entries = s["final_schedule"]["rendered_schedule_entries"]
@@ -115,7 +118,7 @@ class PagerDutyApi:
115
118
  ]
116
119
 
117
120
  def get_escalation_policy_users(
118
- self, escalation_policy_id: str, now: dt
121
+ self, escalation_policy_id: str, now: datetime
119
122
  ) -> list[pypd.User]:
120
123
  ep = pypd.EscalationPolicy.fetch(
121
124
  id=escalation_policy_id, since=now, until=now, time_zone="UTC"
@@ -189,7 +192,7 @@ def get_pagerduty_name(user: PagerDutyUser) -> str:
189
192
  return user.pagerduty_username or user.org_username
190
193
 
191
194
 
192
- @retry(no_retry_exceptions=PagerDutyTargetError)
195
+ @retry(no_retry_exceptions=(PagerDutyTargetError,))
193
196
  def get_usernames_from_pagerduty(
194
197
  pagerduties: Iterable[PagerDutyTarget],
195
198
  users: Iterable[PagerDutyUser],
@@ -3,13 +3,12 @@ from collections import defaultdict
3
3
 
4
4
  from pydantic import (
5
5
  BaseModel,
6
- Extra,
7
6
  )
8
7
 
9
8
  from reconcile.utils.state import State
10
9
 
11
10
 
12
- class PromotionData(BaseModel):
11
+ class PromotionData(BaseModel, extra="forbid"):
13
12
  """
14
13
  A class that strictly corresponds to the json stored in S3.
15
14
 
@@ -20,17 +19,13 @@ class PromotionData(BaseModel):
20
19
 
21
20
  # The success is primarily used for SAPM auto-promotions
22
21
  success: bool
23
- target_config_hash: str | None
24
- saas_file: str | None
25
- check_in: str | None
22
+ target_config_hash: str | None = None
23
+ saas_file: str | None = None
24
+ check_in: str | None = None
26
25
  # Whether this promotion has ever succeeded
27
26
  # Note, this shouldnt be overridden on subsequent promotions of same ref
28
27
  # This attribute is primarily used by saasherder validations
29
- has_succeeded_once: bool | None
30
-
31
- class Config:
32
- smart_union = True
33
- extra = Extra.forbid
28
+ has_succeeded_once: bool | None = None
34
29
 
35
30
 
36
31
  class PromotionState:
@@ -107,5 +102,5 @@ class PromotionState:
107
102
  self, sha: str, channel: str, target_uid: str, data: PromotionData
108
103
  ) -> None:
109
104
  state_key_v2 = f"promotions_v2/{channel}/{target_uid}/{sha}"
110
- self._state.add(state_key_v2, data.dict(), force=True)
105
+ self._state.add(state_key_v2, data.model_dump(), force=True)
111
106
  logging.info("Uploaded %s to %s", data, state_key_v2)
@@ -1,8 +1,11 @@
1
1
  import os
2
+ from typing import Any
2
3
 
3
4
  import requests
4
5
  from sretoolbox.utils import retry
5
6
 
7
+ Headers = dict[str, str | bytes | None]
8
+
6
9
 
7
10
  class RawGithubApi:
8
11
  """
@@ -18,10 +21,10 @@ class RawGithubApi:
18
21
  "application/vnd.github.dazzler-preview+json"
19
22
  }
20
23
 
21
- def __init__(self, password):
24
+ def __init__(self, password: str) -> None:
22
25
  self.password = password
23
26
 
24
- def headers(self, headers=None):
27
+ def headers(self, headers: Headers | None = None) -> Headers:
25
28
  if headers is None:
26
29
  headers = {}
27
30
  new_headers = headers.copy()
@@ -29,13 +32,13 @@ class RawGithubApi:
29
32
  new_headers["Authorization"] = "token %s" % (self.password,)
30
33
  return new_headers
31
34
 
32
- def patch(self, url):
35
+ def patch(self, url: str) -> requests.Response:
33
36
  res = requests.patch(url, headers=self.headers(), timeout=60)
34
37
  res.raise_for_status()
35
38
  return res
36
39
 
37
40
  @retry()
38
- def query(self, url, headers=None):
41
+ def query(self, url: str, headers: Headers | None = None) -> Any:
39
42
  if headers is None:
40
43
  headers = {}
41
44
  h = self.headers(headers)
@@ -64,7 +67,7 @@ class RawGithubApi:
64
67
 
65
68
  return result
66
69
 
67
- def org_invitations(self, org):
70
+ def org_invitations(self, org: str) -> list[str]:
68
71
  invitations = self.query(f"/orgs/{org}/invitations")
69
72
 
70
73
  return [
@@ -73,7 +76,7 @@ class RawGithubApi:
73
76
  if login is not None
74
77
  ]
75
78
 
76
- def team_invitations(self, org_id, team_id):
79
+ def team_invitations(self, org_id: str | int, team_id: str | int) -> list[str]:
77
80
  invitations = self.query(f"/organizations/{org_id}/team/{team_id}/invitations")
78
81
 
79
82
  return [
@@ -82,10 +85,10 @@ class RawGithubApi:
82
85
  if login is not None
83
86
  ]
84
87
 
85
- def repo_invitations(self):
88
+ def repo_invitations(self) -> list[dict[str, Any]]:
86
89
  return self.query("/user/repository_invitations")
87
90
 
88
- def accept_repo_invitation(self, invitation_id):
91
+ def accept_repo_invitation(self, invitation_id: int) -> None:
89
92
  url = self.BASE_URL + f"/user/repository_invitations/{invitation_id}"
90
93
  res = self.patch(url)
91
94
  res.raise_for_status()