qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
  3. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
  4. reconcile/acs_rbac.py +2 -2
  5. reconcile/aus/advanced_upgrade_service.py +18 -12
  6. reconcile/aus/aus_sts_gate_handler.py +59 -0
  7. reconcile/aus/base.py +137 -34
  8. reconcile/aus/cluster_version_data.py +15 -5
  9. reconcile/aus/models.py +3 -1
  10. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  11. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  12. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  13. reconcile/aus/version_gate_approver.py +1 -16
  14. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
  15. reconcile/automated_actions/config/integration.py +16 -4
  16. reconcile/aws_account_manager/integration.py +21 -9
  17. reconcile/aws_account_manager/reconciler.py +3 -3
  18. reconcile/aws_account_manager/utils.py +1 -1
  19. reconcile/aws_ami_cleanup/integration.py +8 -12
  20. reconcile/aws_ami_share.py +69 -62
  21. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  22. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  23. reconcile/aws_iam_keys.py +1 -0
  24. reconcile/aws_saml_idp/integration.py +12 -4
  25. reconcile/aws_saml_roles/integration.py +30 -23
  26. reconcile/aws_version_sync/integration.py +6 -12
  27. reconcile/change_owners/README.md +1 -1
  28. reconcile/change_owners/bundle.py +3 -3
  29. reconcile/change_owners/change_log_tracking.py +3 -2
  30. reconcile/change_owners/change_owners.py +108 -42
  31. reconcile/change_owners/decision.py +1 -1
  32. reconcile/change_owners/diff.py +0 -2
  33. reconcile/checkpoint.py +11 -3
  34. reconcile/cli.py +94 -11
  35. reconcile/dashdotdb_dora.py +5 -12
  36. reconcile/dashdotdb_slo.py +1 -1
  37. reconcile/database_access_manager.py +123 -117
  38. reconcile/dynatrace_token_provider/integration.py +1 -1
  39. reconcile/endpoints_discovery/integration.py +4 -1
  40. reconcile/endpoints_discovery/merge_request.py +1 -1
  41. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  42. reconcile/external_resources/factories.py +4 -6
  43. reconcile/external_resources/integration.py +1 -1
  44. reconcile/external_resources/manager.py +8 -6
  45. reconcile/external_resources/meta.py +0 -1
  46. reconcile/external_resources/metrics.py +1 -1
  47. reconcile/external_resources/model.py +19 -15
  48. reconcile/external_resources/reconciler.py +7 -4
  49. reconcile/external_resources/secrets_sync.py +6 -10
  50. reconcile/external_resources/state.py +26 -16
  51. reconcile/fleet_labeler/integration.py +1 -1
  52. reconcile/gabi_authorized_users.py +5 -2
  53. reconcile/gcp_image_mirror.py +2 -2
  54. reconcile/github_org.py +1 -1
  55. reconcile/github_owners.py +4 -0
  56. reconcile/gitlab_housekeeping.py +13 -15
  57. reconcile/gitlab_members.py +6 -12
  58. reconcile/gitlab_owners.py +15 -11
  59. reconcile/gitlab_permissions.py +8 -12
  60. reconcile/glitchtip_project_alerts/integration.py +3 -1
  61. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  62. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  63. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  64. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  65. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  66. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  67. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  68. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  69. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  70. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
  71. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  72. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  73. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  74. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  75. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  76. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  77. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  78. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  79. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  80. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  81. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  84. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  85. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  87. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  88. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  89. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  90. reconcile/gql_definitions/common/apps.py +5 -5
  91. reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
  92. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  93. reconcile/gql_definitions/common/clusters.py +7 -5
  94. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  95. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  96. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  97. reconcile/gql_definitions/common/github_orgs.py +5 -5
  98. reconcile/gql_definitions/common/jira_settings.py +5 -5
  99. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  100. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  101. reconcile/gql_definitions/common/namespaces.py +5 -5
  102. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  103. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  104. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  105. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  106. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  107. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  108. reconcile/gql_definitions/common/quay_instances.py +5 -5
  109. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  110. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  111. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  112. reconcile/gql_definitions/common/saas_files.py +5 -5
  113. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  114. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  115. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  116. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  117. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  118. reconcile/gql_definitions/common/users.py +5 -5
  119. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  120. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  121. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  122. reconcile/gql_definitions/cost_report/settings.py +5 -5
  123. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  124. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  125. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  126. reconcile/gql_definitions/email_sender/apps.py +5 -5
  127. reconcile/gql_definitions/email_sender/emails.py +5 -5
  128. reconcile/gql_definitions/email_sender/users.py +5 -5
  129. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  130. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  131. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  132. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
  133. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  134. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  135. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  136. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  138. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  139. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  141. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  142. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  143. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  144. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  145. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  146. reconcile/gql_definitions/fragments/disable.py +5 -5
  147. reconcile/gql_definitions/fragments/email_service.py +5 -5
  148. reconcile/gql_definitions/fragments/email_user.py +5 -5
  149. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  150. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  151. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  152. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  153. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  154. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  155. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  156. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  157. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  158. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  159. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  160. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  161. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  162. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  163. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  164. reconcile/gql_definitions/fragments/user.py +5 -5
  165. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  166. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  167. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  168. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  169. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  170. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  171. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  172. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  173. reconcile/gql_definitions/integrations/integrations.py +5 -5
  174. reconcile/gql_definitions/introspection.json +775 -136
  175. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  176. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  177. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  178. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  179. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  180. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  181. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  182. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  183. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  184. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  185. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  186. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  187. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  188. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  189. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  190. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  191. reconcile/gql_definitions/rhcs/certs.py +25 -79
  192. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  193. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  194. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  195. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  196. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  197. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  198. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  199. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  200. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  201. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  202. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  203. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  204. reconcile/gql_definitions/status_board/status_board.py +5 -5
  205. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  206. reconcile/gql_definitions/templating/template_collection.py +5 -5
  207. reconcile/gql_definitions/templating/templates.py +5 -5
  208. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  209. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  210. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  211. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  212. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  213. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  214. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  215. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  216. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  217. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
  218. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  219. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  220. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  221. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  222. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
  223. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
  224. reconcile/integrations_manager.py +3 -3
  225. reconcile/jenkins_worker_fleets.py +10 -8
  226. reconcile/jira_permissions_validator.py +237 -122
  227. reconcile/ldap_groups/integration.py +1 -1
  228. reconcile/ocm/types.py +35 -57
  229. reconcile/ocm_aws_infrastructure_access.py +1 -1
  230. reconcile/ocm_clusters.py +4 -4
  231. reconcile/ocm_labels/integration.py +3 -2
  232. reconcile/ocm_machine_pools.py +33 -27
  233. reconcile/openshift_base.py +113 -4
  234. reconcile/openshift_cluster_bots.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +96 -101
  237. reconcile/openshift_resources_base.py +6 -2
  238. reconcile/openshift_rhcs_certs.py +74 -37
  239. reconcile/openshift_rolebindings.py +7 -11
  240. reconcile/openshift_saas_deploy.py +4 -5
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +2 -2
  244. reconcile/openshift_upgrade_watcher.py +4 -4
  245. reconcile/oum/labelset.py +5 -3
  246. reconcile/oum/models.py +1 -4
  247. reconcile/prometheus_rules_tester/integration.py +3 -3
  248. reconcile/quay_base.py +25 -6
  249. reconcile/quay_membership.py +55 -29
  250. reconcile/quay_mirror.py +1 -1
  251. reconcile/quay_mirror_org.py +6 -4
  252. reconcile/quay_permissions.py +81 -75
  253. reconcile/quay_repos.py +35 -37
  254. reconcile/queries.py +132 -1
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +16 -5
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/skupper_network/integration.py +2 -2
  260. reconcile/slack_usergroups.py +35 -14
  261. reconcile/sql_query.py +1 -0
  262. reconcile/status_board.py +6 -6
  263. reconcile/statuspage/atlassian.py +7 -7
  264. reconcile/statuspage/integrations/maintenances.py +4 -3
  265. reconcile/statuspage/page.py +4 -9
  266. reconcile/statuspage/status.py +5 -8
  267. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  268. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  269. reconcile/templating/lib/rendering.py +3 -3
  270. reconcile/templating/renderer.py +2 -2
  271. reconcile/templating/validator.py +4 -4
  272. reconcile/terraform_aws_route53.py +7 -1
  273. reconcile/terraform_cloudflare_dns.py +3 -3
  274. reconcile/terraform_cloudflare_resources.py +5 -5
  275. reconcile/terraform_cloudflare_users.py +3 -2
  276. reconcile/terraform_init/integration.py +187 -23
  277. reconcile/terraform_repo.py +16 -12
  278. reconcile/terraform_resources.py +6 -6
  279. reconcile/terraform_tgw_attachments.py +27 -19
  280. reconcile/terraform_users.py +7 -0
  281. reconcile/terraform_vpc_peerings.py +14 -3
  282. reconcile/terraform_vpc_resources/integration.py +20 -8
  283. reconcile/terraform_vpc_resources/merge_request.py +12 -2
  284. reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
  285. reconcile/typed_queries/aws_account_tags.py +41 -0
  286. reconcile/typed_queries/cost_report/app_names.py +1 -1
  287. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  288. reconcile/typed_queries/saas_files.py +20 -15
  289. reconcile/typed_queries/status_board.py +2 -2
  290. reconcile/unleash_feature_toggles/integration.py +4 -2
  291. reconcile/utils/acs/base.py +6 -3
  292. reconcile/utils/acs/policies.py +2 -2
  293. reconcile/utils/aws_api.py +51 -20
  294. reconcile/utils/aws_api_typed/api.py +38 -9
  295. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  296. reconcile/utils/aws_api_typed/logs.py +73 -0
  297. reconcile/utils/aws_api_typed/organization.py +4 -2
  298. reconcile/utils/binary.py +7 -12
  299. reconcile/utils/datetime_util.py +67 -0
  300. reconcile/utils/deadmanssnitch_api.py +1 -1
  301. reconcile/utils/differ.py +2 -3
  302. reconcile/utils/early_exit_cache.py +11 -12
  303. reconcile/utils/environ.py +5 -0
  304. reconcile/utils/expiration.py +7 -3
  305. reconcile/utils/external_resource_spec.py +2 -0
  306. reconcile/utils/filtering.py +1 -1
  307. reconcile/utils/gitlab_api.py +19 -5
  308. reconcile/utils/glitchtip/client.py +6 -2
  309. reconcile/utils/glitchtip/models.py +25 -28
  310. reconcile/utils/gql.py +4 -7
  311. reconcile/utils/helpers.py +1 -1
  312. reconcile/utils/instrumented_wrappers.py +1 -1
  313. reconcile/utils/internal_groups/client.py +2 -2
  314. reconcile/utils/internal_groups/models.py +8 -17
  315. reconcile/utils/jinja2/utils.py +6 -101
  316. reconcile/utils/jira_client.py +82 -63
  317. reconcile/utils/jjb_client.py +26 -13
  318. reconcile/utils/jobcontroller/controller.py +2 -2
  319. reconcile/utils/jobcontroller/models.py +17 -1
  320. reconcile/utils/json.py +43 -1
  321. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  322. reconcile/utils/membershipsources/models.py +16 -23
  323. reconcile/utils/membershipsources/resolver.py +4 -2
  324. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  325. reconcile/utils/merge_request_manager/parser.py +6 -6
  326. reconcile/utils/metrics.py +5 -5
  327. reconcile/utils/models.py +304 -82
  328. reconcile/utils/mr/app_interface_reporter.py +2 -2
  329. reconcile/utils/mr/notificator.py +3 -3
  330. reconcile/utils/mr/update_access_report_base.py +3 -4
  331. reconcile/utils/mr/user_maintenance.py +3 -2
  332. reconcile/utils/oc.py +252 -201
  333. reconcile/utils/oc_filters.py +3 -3
  334. reconcile/utils/ocm/addons.py +0 -1
  335. reconcile/utils/ocm/base.py +17 -20
  336. reconcile/utils/ocm/cluster_groups.py +1 -1
  337. reconcile/utils/ocm/identity_providers.py +2 -2
  338. reconcile/utils/ocm/labels.py +1 -1
  339. reconcile/utils/ocm/products.py +8 -8
  340. reconcile/utils/ocm/search_filters.py +3 -6
  341. reconcile/utils/ocm/service_log.py +4 -6
  342. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  343. reconcile/utils/openshift_resource.py +8 -3
  344. reconcile/utils/pagerduty_api.py +10 -7
  345. reconcile/utils/promotion_state.py +6 -11
  346. reconcile/utils/quay_api.py +74 -87
  347. reconcile/utils/raw_github_api.py +1 -1
  348. reconcile/utils/rhcsv2_certs.py +86 -23
  349. reconcile/utils/rosa/session.py +16 -0
  350. reconcile/utils/runtime/integration.py +2 -3
  351. reconcile/utils/runtime/runner.py +2 -2
  352. reconcile/utils/saasherder/interfaces.py +13 -20
  353. reconcile/utils/saasherder/models.py +23 -20
  354. reconcile/utils/saasherder/saasherder.py +50 -27
  355. reconcile/utils/slack_api.py +2 -2
  356. reconcile/utils/sloth.py +171 -2
  357. reconcile/utils/structs.py +1 -1
  358. reconcile/utils/terraform_client.py +5 -4
  359. reconcile/utils/terrascript_aws_client.py +274 -124
  360. reconcile/utils/unleash/server.py +2 -8
  361. reconcile/utils/vault.py +5 -12
  362. reconcile/utils/vcs.py +8 -8
  363. reconcile/vault_replication.py +107 -42
  364. reconcile/vpc_peerings_validator.py +13 -0
  365. tools/app_interface_reporter.py +4 -4
  366. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  367. tools/cli_commands/cost_report/view.py +7 -6
  368. tools/cli_commands/erv2.py +1 -1
  369. tools/qontract_cli.py +28 -17
  370. tools/template_validation.py +3 -1
  371. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
reconcile/utils/oc.py CHANGED
@@ -10,16 +10,16 @@ import re
10
10
  import subprocess
11
11
  import threading
12
12
  import time
13
+ from collections import defaultdict
13
14
  from contextlib import suppress
14
15
  from dataclasses import dataclass
15
- from datetime import datetime
16
16
  from functools import cache, wraps
17
17
  from subprocess import Popen
18
18
  from threading import Lock
19
19
  from typing import TYPE_CHECKING, Any, TextIO, cast
20
20
 
21
21
  import urllib3
22
- from kubernetes.client import ( # type: ignore[attr-defined]
22
+ from kubernetes.client import (
23
23
  ApiClient,
24
24
  Configuration,
25
25
  )
@@ -68,7 +68,18 @@ if TYPE_CHECKING:
68
68
  urllib3.disable_warnings()
69
69
 
70
70
  GET_REPLICASET_MAX_ATTEMPTS = 20
71
-
71
+ DEFAULT_GROUP = ""
72
+ PROJECT_KIND = "Project.project.openshift.io"
73
+ POD_RECYCLE_SUPPORTED_TRIGGER_KINDS = [
74
+ "ConfigMap",
75
+ "Secret",
76
+ ]
77
+ POD_RECYCLE_SUPPORTED_OWNER_KINDS = [
78
+ "DaemonSet",
79
+ "Deployment",
80
+ "DeploymentConfig",
81
+ "StatefulSet",
82
+ ]
72
83
 
73
84
  oc_run_execution_counter = Counter(
74
85
  name="oc_run_execution_counter",
@@ -125,23 +136,23 @@ class JSONParsingError(Exception):
125
136
  pass
126
137
 
127
138
 
128
- class RecyclePodsUnsupportedKindError(Exception):
139
+ class PodNotReadyError(Exception):
129
140
  pass
130
141
 
131
142
 
132
- class RecyclePodsInvalidAnnotationValueError(Exception):
143
+ class JobNotRunningError(Exception):
133
144
  pass
134
145
 
135
146
 
136
- class PodNotReadyError(Exception):
147
+ class RequestEntityTooLargeError(Exception):
137
148
  pass
138
149
 
139
150
 
140
- class JobNotRunningError(Exception):
151
+ class KindNotFoundError(Exception):
141
152
  pass
142
153
 
143
154
 
144
- class RequestEntityTooLargeError(Exception):
155
+ class AmbiguousResourceTypeError(Exception):
145
156
  pass
146
157
 
147
158
 
@@ -380,10 +391,7 @@ class OCCli:
380
391
 
381
392
  self.init_projects = init_projects
382
393
  if self.init_projects:
383
- if self.is_kind_supported("Project"):
384
- kind = "Project.project.openshift.io"
385
- else:
386
- kind = "Namespace"
394
+ kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
387
395
  self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
388
396
 
389
397
  self.slow_oc_reconcile_threshold = float(
@@ -453,10 +461,7 @@ class OCCli:
453
461
 
454
462
  self.init_projects = init_projects
455
463
  if self.init_projects:
456
- if self.is_kind_supported("Project"):
457
- kind = "Project.project.openshift.io"
458
- else:
459
- kind = "Namespace"
464
+ kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
460
465
  self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
461
466
 
462
467
  self.slow_oc_reconcile_threshold = float(
@@ -637,20 +642,24 @@ class OCCli:
637
642
  def project_exists(self, name: str) -> bool:
638
643
  if name in self.projects:
639
644
  return True
645
+ kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
640
646
  try:
641
- if self.is_kind_supported("Project"):
642
- self.get(None, "Project.project.openshift.io", name)
643
- else:
644
- self.get(None, "Namespace", name)
647
+ self.get(None, kind, name)
645
648
  except StatusCodeError as e:
646
649
  if "NotFound" in str(e):
647
650
  return False
648
651
  raise e
649
652
  return True
650
653
 
654
+ def _use_oc_project(self, namespace: str) -> bool:
655
+ # Note, that openshift-* namespaces cannot be created via new-project
656
+ return self.is_kind_supported(PROJECT_KIND) and not namespace.startswith(
657
+ "openshift-"
658
+ )
659
+
651
660
  @OCDecorators.process_reconcile_time
652
661
  def new_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
653
- if self.is_kind_supported("Project"):
662
+ if self._use_oc_project(namespace=namespace):
654
663
  cmd = ["new-project", namespace]
655
664
  else:
656
665
  cmd = ["create", "namespace", namespace]
@@ -666,7 +675,7 @@ class OCCli:
666
675
 
667
676
  @OCDecorators.process_reconcile_time
668
677
  def delete_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
669
- if self.is_kind_supported("Project"):
678
+ if self._use_oc_project(namespace=namespace):
670
679
  cmd = ["delete", "project", namespace]
671
680
  else:
672
681
  cmd = ["delete", "namespace", namespace]
@@ -715,9 +724,9 @@ class OCCli:
715
724
 
716
725
  def sa_get_token(self, namespace: str, name: str) -> str:
717
726
  cmd = ["sa", "-n", namespace, "get-token", name]
718
- return self._run(cmd)
727
+ return self._run(cmd).decode("utf-8")
719
728
 
720
- def get_api_resources(self) -> dict[str, Any]:
729
+ def get_api_resources(self) -> dict[str, list[OCCliApiResource]]:
721
730
  with self.api_resources_lock:
722
731
  if not self.api_resources:
723
732
  cmd = ["api-resources", "--no-headers"]
@@ -921,108 +930,105 @@ class OCCli:
921
930
  if not status["ready"]:
922
931
  raise PodNotReadyError(name)
923
932
 
924
- def recycle_pods(
925
- self, dry_run: bool, namespace: str, dep_kind: str, dep_resource: OR
926
- ) -> None:
927
- """recycles pods which are using the specified resources.
928
- will only act on Secrets containing the 'qontract.recycle' annotation.
929
- dry_run: simulate pods recycle.
930
- namespace: namespace in which dependant resource is applied.
931
- dep_kind: dependant resource kind. currently only supports Secret.
932
- dep_resource: dependant resource."""
933
-
934
- supported_kinds = ["Secret", "ConfigMap"]
935
- if dep_kind not in supported_kinds:
933
+ def _is_resource_supported_to_trigger_recycle(
934
+ self,
935
+ namespace: str,
936
+ resource: OR,
937
+ ) -> bool:
938
+ if resource.kind not in POD_RECYCLE_SUPPORTED_TRIGGER_KINDS:
936
939
  logging.debug([
937
940
  "skipping_pod_recycle_unsupported",
938
941
  self.cluster_name,
939
942
  namespace,
940
- dep_kind,
943
+ resource.kind,
944
+ resource.name,
941
945
  ])
942
- return
946
+ return False
943
947
 
944
- dep_annotations = dep_resource.body["metadata"].get("annotations", {})
945
948
  # Note, that annotations might have been set to None explicitly
946
- dep_annotations = dep_resource.body["metadata"].get("annotations") or {}
947
- qontract_recycle = dep_annotations.get("qontract.recycle")
948
- if qontract_recycle is True:
949
- raise RecyclePodsInvalidAnnotationValueError('should be "true"')
949
+ annotations = resource.body["metadata"].get("annotations") or {}
950
+ qontract_recycle = annotations.get("qontract.recycle")
950
951
  if qontract_recycle != "true":
951
952
  logging.debug([
952
953
  "skipping_pod_recycle_no_annotation",
953
954
  self.cluster_name,
954
955
  namespace,
955
- dep_kind,
956
+ resource.kind,
957
+ resource.name,
956
958
  ])
959
+ return False
960
+ return True
961
+
962
+ def recycle_pods(
963
+ self,
964
+ dry_run: bool,
965
+ namespace: str,
966
+ resource: OR,
967
+ ) -> None:
968
+ """
969
+ recycles pods which are using the specified resources.
970
+ will only act on Secret or ConfigMap containing the 'qontract.recycle' annotation.
971
+
972
+ Args:
973
+ dry_run (bool): if True, will only log the recycle action without executing it
974
+ namespace (str): namespace of the resource
975
+ resource (OR): resource object (Secret or ConfigMap) to check for pod usage
976
+ """
977
+
978
+ if not self._is_resource_supported_to_trigger_recycle(namespace, resource):
957
979
  return
958
980
 
959
- dep_name = dep_resource.name
960
981
  pods = self.get(namespace, "Pod")["items"]
961
-
962
- if dep_kind == "Secret":
963
- pods_to_recycle = [
964
- pod for pod in pods if self.secret_used_in_pod(dep_name, pod)
965
- ]
966
- elif dep_kind == "ConfigMap":
967
- pods_to_recycle = [
968
- pod for pod in pods if self.configmap_used_in_pod(dep_name, pod)
969
- ]
970
- else:
971
- raise RecyclePodsUnsupportedKindError(dep_kind)
972
-
973
- recyclables: dict[str, list[dict[str, Any]]] = {}
974
- supported_recyclables = [
975
- "Deployment",
976
- "DeploymentConfig",
977
- "StatefulSet",
978
- "DaemonSet",
982
+ pods_to_recycle = [
983
+ pod
984
+ for pod in pods
985
+ if self.is_resource_used_in_pod(
986
+ name=resource.name,
987
+ kind=resource.kind,
988
+ pod=pod,
989
+ )
979
990
  ]
991
+
992
+ recycle_names_by_kind = defaultdict(set)
980
993
  for pod in pods_to_recycle:
981
994
  owner = self.get_obj_root_owner(namespace, pod, allow_not_found=True)
982
995
  kind = owner["kind"]
983
- if kind not in supported_recyclables:
984
- continue
985
- recyclables.setdefault(kind, [])
986
- exists = False
987
- for obj in recyclables[kind]:
988
- owner_name = owner["metadata"]["name"]
989
- if obj["metadata"]["name"] == owner_name:
990
- exists = True
991
- break
992
- if not exists:
993
- recyclables[kind].append(owner)
994
-
995
- for kind, objs in recyclables.items():
996
- for obj in objs:
997
- self.recycle(dry_run, namespace, kind, obj)
998
-
999
- @retry(exceptions=ObjectHasBeenModifiedError)
996
+ if kind in POD_RECYCLE_SUPPORTED_OWNER_KINDS:
997
+ recycle_names_by_kind[kind].add(owner["metadata"]["name"])
998
+
999
+ for kind, names in recycle_names_by_kind.items():
1000
+ for name in names:
1001
+ self.recycle(
1002
+ dry_run=dry_run,
1003
+ namespace=namespace,
1004
+ kind=kind,
1005
+ name=name,
1006
+ )
1007
+
1000
1008
  def recycle(
1001
- self, dry_run: bool, namespace: str, kind: str, obj: MutableMapping[str, Any]
1009
+ self,
1010
+ dry_run: bool,
1011
+ namespace: str,
1012
+ kind: str,
1013
+ name: str,
1002
1014
  ) -> None:
1003
- """Recycles an object by adding a recycle.time annotation
1015
+ """
1016
+ Recycles an object using oc rollout restart, which will add an annotation
1017
+ kubectl.kubernetes.io/restartedAt with the current timestamp to the pod
1018
+ template, triggering a rolling restart.
1004
1019
 
1005
- :param dry_run: Is this a dry run
1006
- :param namespace: Namespace to work in
1007
- :param kind: Object kind
1008
- :param obj: Object to recycle
1020
+ Args:
1021
+ dry_run (bool): if True, will only log the recycle action without executing it
1022
+ namespace (str): namespace of the object to recycle
1023
+ kind (str): kind of the object to recycle
1024
+ name (str): name of the object to recycle
1009
1025
  """
1010
- name = obj["metadata"]["name"]
1011
1026
  logging.info([f"recycle_{kind.lower()}", self.cluster_name, namespace, name])
1012
1027
  if not dry_run:
1013
- now = datetime.now()
1014
- recycle_time = now.strftime("%d/%m/%Y %H:%M:%S")
1015
-
1016
- # get the object in case it was modified
1017
- obj = self.get(namespace, kind, name)
1018
- # honor update strategy by setting annotations to force
1019
- # a new rollout
1020
- a = obj["spec"]["template"]["metadata"].get("annotations", {})
1021
- a["recycle.time"] = recycle_time
1022
- obj["spec"]["template"]["metadata"]["annotations"] = a
1023
- cmd = ["apply", "-n", namespace, "-f", "-"]
1024
- stdin = json_dumps(obj)
1025
- self._run(cmd, stdin=stdin, apply=True)
1028
+ self._run(
1029
+ ["rollout", "restart", f"{kind}/{name}", "-n", namespace],
1030
+ apply=True,
1031
+ )
1026
1032
 
1027
1033
  def get_obj_root_owner(
1028
1034
  self,
@@ -1064,12 +1070,24 @@ class OCCli:
1064
1070
  )
1065
1071
  return obj
1066
1072
 
1067
- def secret_used_in_pod(self, name: str, pod: Mapping[str, Any]) -> bool:
1068
- used_resources = self.get_resources_used_in_pod_spec(pod["spec"], "Secret")
1069
- return name in used_resources
1073
+ def is_resource_used_in_pod(
1074
+ self,
1075
+ name: str,
1076
+ kind: str,
1077
+ pod: Mapping[str, Any],
1078
+ ) -> bool:
1079
+ """
1080
+ Check if a resource (Secret or ConfigMap) is used in a Pod.
1081
+
1082
+ Args:
1083
+ name: Name of the resource
1084
+ kind: "Secret" or "ConfigMap"
1085
+ pod: Pod object
1070
1086
 
1071
- def configmap_used_in_pod(self, name: str, pod: Mapping[str, Any]) -> bool:
1072
- used_resources = self.get_resources_used_in_pod_spec(pod["spec"], "ConfigMap")
1087
+ Returns:
1088
+ True if the resource is used in the Pod, False otherwise.
1089
+ """
1090
+ used_resources = self.get_resources_used_in_pod_spec(pod["spec"], kind)
1073
1091
  return name in used_resources
1074
1092
 
1075
1093
  @staticmethod
@@ -1078,25 +1096,39 @@ class OCCli:
1078
1096
  kind: str,
1079
1097
  include_optional: bool = True,
1080
1098
  ) -> dict[str, set[str]]:
1081
- if kind not in {"Secret", "ConfigMap"}:
1082
- raise KeyError(f"unsupported resource kind: {kind}")
1099
+ """
1100
+ Get resources (Secrets or ConfigMaps) used in a Pod spec.
1101
+ Returns a dictionary where keys are resource names and values are sets of keys used from that resource.
1102
+
1103
+ Args:
1104
+ spec: Pod spec
1105
+ kind: "Secret" or "ConfigMap"
1106
+ include_optional: Whether to include optional resources
1107
+
1108
+ Returns:
1109
+ A dictionary mapping resource names to sets of keys used.
1110
+ """
1111
+ match kind:
1112
+ case "Secret":
1113
+ volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
1114
+ "secret",
1115
+ "secretName",
1116
+ "secretRef",
1117
+ "secretKeyRef",
1118
+ "name",
1119
+ )
1120
+ case "ConfigMap":
1121
+ volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
1122
+ "configMap",
1123
+ "name",
1124
+ "configMapRef",
1125
+ "configMapKeyRef",
1126
+ "name",
1127
+ )
1128
+ case _:
1129
+ raise KeyError(f"unsupported resource kind: {kind}")
1130
+
1083
1131
  optional = "optional"
1084
- if kind == "Secret":
1085
- volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
1086
- "secret",
1087
- "secretName",
1088
- "secretRef",
1089
- "secretKeyRef",
1090
- "name",
1091
- )
1092
- elif kind == "ConfigMap":
1093
- volume_kind, volume_kind_ref, env_from_kind, env_kind, env_ref = (
1094
- "configMap",
1095
- "name",
1096
- "configMapRef",
1097
- "configMapKeyRef",
1098
- "name",
1099
- )
1100
1132
 
1101
1133
  resources: dict[str, set[str]] = {}
1102
1134
  for v in spec.get("volumes") or []:
@@ -1125,8 +1157,8 @@ class OCCli:
1125
1157
  continue
1126
1158
  resource_name = resource_ref[env_ref]
1127
1159
  resources.setdefault(resource_name, set())
1128
- secret_key = resource_ref["key"]
1129
- resources[resource_name].add(secret_key)
1160
+ key = resource_ref["key"]
1161
+ resources[resource_name].add(key)
1130
1162
  except (KeyError, TypeError):
1131
1163
  continue
1132
1164
 
@@ -1187,7 +1219,7 @@ class OCCli:
1187
1219
  def _run_json(
1188
1220
  self, cmd: list[str], allow_not_found: bool = False
1189
1221
  ) -> dict[str, Any]:
1190
- out = self._run(cmd, allow_not_found=allow_not_found)
1222
+ out = self._run(cmd, allow_not_found=allow_not_found).decode("utf-8")
1191
1223
 
1192
1224
  try:
1193
1225
  out_json = json.loads(out)
@@ -1196,76 +1228,90 @@ class OCCli:
1196
1228
 
1197
1229
  return out_json
1198
1230
 
1199
- def _parse_kind(self, kind_name: str) -> tuple[str, str]:
1200
- # This is a provisional solution while we work in redefining
1201
- # the api resources initialization.
1202
- if not self.api_resources:
1203
- self.get_api_resources()
1231
+ def parse_kind(self, kind: str) -> tuple[str, str, str]:
1232
+ """Parse a Kubernetes kind string into its components.
1204
1233
 
1205
- kind_group = kind_name.split(".", 1)
1206
- kind = kind_group[0]
1207
- if kind in self.api_resources:
1208
- group_version = self.api_resources[kind][0].group_version
1209
- else:
1210
- raise StatusCodeError(f"{self.server}: {kind} does not exist")
1211
-
1212
- # if a kind_group has more than 1 entry than the kind_name is in
1213
- # the format kind.apigroup. Find the apigroup/version that matches
1214
- # the apigroup passed with the kind_name
1215
- if len(kind_group) > 1:
1216
- apigroup_override = kind_group[1]
1217
- find = False
1218
- for gv in self.api_resources[kind]:
1219
- if apigroup_override == gv.group:
1220
- if not gv.group:
1221
- group_version = gv.api_version
1222
- else:
1223
- group_version = f"{gv.group}/{gv.api_version}"
1224
- find = True
1225
- break
1226
-
1227
- if not find:
1228
- raise StatusCodeError(
1229
- f"{self.server}: {apigroup_override} does not have kind {kind}"
1230
- )
1231
- return (kind, group_version)
1234
+ Supports three formats:
1235
+ - kind
1236
+ - kind.group.whatever
1237
+ - kind.group.whatever/version
1238
+
1239
+ Args:
1240
+ kind: A Kubernetes kind string in one of the supported formats
1241
+
1242
+ Returns:
1243
+ Tuple of (kind, group, version) where missing parts are empty strings
1244
+
1245
+ Raises:
1246
+ ValueError: If the kind string format is invalid
1247
+
1248
+ Examples:
1249
+ >>> parse_kind_string("Deployment")
1250
+ ('Deployment', '', '')
1251
+ >>> parse_kind_string("ClusterRoleBinding.rbac.authorization.k8s.io")
1252
+ ('ClusterRoleBinding', 'rbac.authorization.k8s.io', '')
1253
+ >>> parse_kind_string("CustomResource.mygroup.example.com/v1")
1254
+ ('CustomResource', 'mygroup.example.com', 'v1')
1255
+ """
1256
+ pattern = r"^(?P<kind>[^./]+)(?:\.(?P<group>[^/]+))?(?:/(?P<version>.+))?$"
1257
+ match = re.match(pattern, kind)
1258
+ if not match:
1259
+ raise ValueError(f"Invalid kind string: {kind}")
1260
+
1261
+ kind = match.group("kind") or ""
1262
+ group = match.group("group") or DEFAULT_GROUP
1263
+ version = match.group("version") or ""
1264
+
1265
+ return kind, group, version
1232
1266
 
1233
1267
  def is_kind_supported(self, kind: str) -> bool:
1234
- # This is a provisional solution while we work in redefining
1235
- # the api resources initialization.
1236
- if not self.api_resources:
1237
- self.get_api_resources()
1268
+ """Returns True if the given kind is supported by the cluster, False otherwise.
1238
1269
 
1239
- if "." in kind:
1240
- try:
1241
- self._parse_kind(kind)
1242
- return True
1243
- except StatusCodeError:
1244
- return False
1245
- else:
1246
- return kind in self.api_resources
1270
+ Kind can be either kind, kind.group or kind.group/version."""
1271
+ try:
1272
+ self.get_api_resource(kind)
1273
+ return True
1274
+ except KindNotFoundError:
1275
+ return False
1247
1276
 
1248
1277
  def is_kind_namespaced(self, kind: str) -> bool:
1249
- # This is a provisional solution while we work in redefining
1250
- # the api resources initialization.
1278
+ """Returns True if the given kind is namespaced, False if it's cluster scoped.
1279
+
1280
+ Kind can be either kind, kind.group or kind.group/version."""
1281
+ return self.get_api_resource(kind).namespaced
1282
+
1283
+ def get_api_resource(self, kind: str) -> OCCliApiResource:
1284
+ """Return the OCCliApiResource for the given resource type.
1285
+
1286
+ Resource type can be either kind, kind.group or kind.group/version.
1287
+ If kind is not unique, group must be specified."""
1288
+
1251
1289
  if not self.api_resources:
1252
- self.get_api_resources()
1290
+ raise RuntimeError("API resources not initialized")
1253
1291
 
1254
- kg = kind.split(".", 1)
1255
- kind = kg[0]
1292
+ kind, group, _ = self.parse_kind(kind)
1256
1293
 
1257
- # Same Kinds might exist in different api groups
1258
- kind_resources = self.api_resources.get(kind)
1259
- if not kind_resources:
1260
- raise StatusCodeError(f"Kind {kind} does not exist in the ApiServer")
1294
+ if not (resources := self.api_resources.get(kind)):
1295
+ # the kind not found at all
1296
+ raise KindNotFoundError(f"Unsupported resource type: {kind}")
1261
1297
 
1262
- if len(kg) > 1:
1263
- group = kg[1]
1264
- for r in kind_resources:
1265
- if group == r.group:
1266
- return r.namespaced
1267
- raise StatusCodeError(f"Kind: {kind} does nod exist in the ApiServer")
1268
- return kind_resources[0].namespaced
1298
+ if len(resources) == 1 and group == DEFAULT_GROUP:
1299
+ return resources[0]
1300
+
1301
+ # get the resource with the specified group
1302
+ if resource := next((r for r in resources if r.group == group), None):
1303
+ return resource
1304
+
1305
+ # no resource with the specified group found
1306
+ if group == DEFAULT_GROUP:
1307
+ message = (
1308
+ f"Ambiguous resource type: {kind}. "
1309
+ "Please fully qualify it with its API group. E.g., ClusterRoleBinding -> ClusterRoleBinding.rbac.authorization.k8s.io"
1310
+ )
1311
+ raise AmbiguousResourceTypeError(message)
1312
+
1313
+ # group was specified but no matching resource found
1314
+ raise KindNotFoundError(f"Unsupported resource type: {kind}")
1269
1315
 
1270
1316
 
1271
1317
  REQUEST_TIMEOUT = 60
@@ -1305,20 +1351,19 @@ class OCNative(OCCli):
1305
1351
 
1306
1352
  server = connection_parameters.server_url
1307
1353
 
1308
- if server:
1309
- self.client = self._get_client(server, token)
1310
- self.api_resources = self.get_api_resources()
1354
+ if not server:
1355
+ raise Exception("Server name is required!")
1311
1356
 
1312
- else:
1313
- raise Exception("A method relies on client/api_kind_version to be set")
1357
+ if not token:
1358
+ raise Exception("Token is required!")
1359
+
1360
+ self.client = self._get_client(server, token)
1361
+ self.api_resources = self.get_api_resources()
1314
1362
 
1315
1363
  self.projects = set()
1316
1364
  self.init_projects = init_projects
1317
1365
  if self.init_projects:
1318
- if self.is_kind_supported("Project"):
1319
- kind = "Project.project.openshift.io"
1320
- else:
1321
- kind = "Namespace"
1366
+ kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
1322
1367
  self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
1323
1368
 
1324
1369
  def __enter__(self) -> OCNative:
@@ -1368,8 +1413,10 @@ class OCNative(OCCli):
1368
1413
 
1369
1414
  @retry(max_attempts=5, exceptions=(ServerTimeoutError))
1370
1415
  def get_items(self, kind: str, **kwargs: Any) -> list[dict[str, Any]]:
1371
- k, group_version = self._parse_kind(kind)
1372
- obj_client = self._get_obj_client(group_version=group_version, kind=k)
1416
+ resource = self.get_api_resource(kind)
1417
+ obj_client = self._get_obj_client(
1418
+ group_version=resource.group_version, kind=resource.kind
1419
+ )
1373
1420
 
1374
1421
  namespace = ""
1375
1422
  if "namespace" in kwargs:
@@ -1421,8 +1468,10 @@ class OCNative(OCCli):
1421
1468
  name: str | None = None,
1422
1469
  allow_not_found: bool = False,
1423
1470
  ) -> dict[str, Any]:
1424
- k, group_version = self._parse_kind(kind)
1425
- obj_client = self._get_obj_client(group_version=group_version, kind=k)
1471
+ resource = self.get_api_resource(kind)
1472
+ obj_client = self._get_obj_client(
1473
+ group_version=resource.group_version, kind=resource.kind
1474
+ )
1426
1475
  try:
1427
1476
  obj = obj_client.get(
1428
1477
  name=name,
@@ -1436,8 +1485,10 @@ class OCNative(OCCli):
1436
1485
  raise StatusCodeError(f"[{self.server}]: {e}") from None
1437
1486
 
1438
1487
  def get_all(self, kind: str, all_namespaces: bool = False) -> dict[str, Any]:
1439
- k, group_version = self._parse_kind(kind)
1440
- obj_client = self._get_obj_client(group_version=group_version, kind=k)
1488
+ resource = self.get_api_resource(kind)
1489
+ obj_client = self._get_obj_client(
1490
+ group_version=resource.group_version, kind=resource.kind
1491
+ )
1441
1492
  try:
1442
1493
  return obj_client.get(_request_timeout=REQUEST_TIMEOUT).to_dict()
1443
1494
  except NotFoundError as e:
@@ -19,19 +19,19 @@ class Namespace(Protocol):
19
19
  NS = TypeVar("NS", bound=Namespace)
20
20
 
21
21
 
22
- def filter_namespaces_by_cluster(
22
+ def filter_namespaces_by_cluster[NS: Namespace](
23
23
  namespaces: Iterable[NS], cluster_names: Iterable[str]
24
24
  ) -> list[NS]:
25
25
  return [n for n in namespaces if n.cluster.name in cluster_names]
26
26
 
27
27
 
28
- def filter_namespaces_by_name(
28
+ def filter_namespaces_by_name[NS: Namespace](
29
29
  namespaces: Iterable[NS], namespace_names: Iterable[str]
30
30
  ) -> list[NS]:
31
31
  return [n for n in namespaces if n.name in namespace_names]
32
32
 
33
33
 
34
- def filter_namespaces_by_cluster_and_namespace(
34
+ def filter_namespaces_by_cluster_and_namespace[NS: Namespace](
35
35
  namespaces: Iterable[NS],
36
36
  cluster_names: Iterable[str] | None,
37
37
  namespace_names: Iterable[str] | None,