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

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

Potentially problematic release.


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

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