qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev430__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 (351) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +351 -345
  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_iam_keys.py +1 -0
  19. reconcile/aws_saml_idp/integration.py +12 -4
  20. reconcile/aws_saml_roles/integration.py +30 -23
  21. reconcile/aws_version_sync/integration.py +6 -12
  22. reconcile/change_owners/bundle.py +3 -3
  23. reconcile/change_owners/change_log_tracking.py +3 -2
  24. reconcile/change_owners/change_owners.py +1 -1
  25. reconcile/change_owners/diff.py +0 -2
  26. reconcile/checkpoint.py +11 -3
  27. reconcile/cli.py +93 -10
  28. reconcile/dashdotdb_dora.py +5 -12
  29. reconcile/dashdotdb_slo.py +1 -1
  30. reconcile/database_access_manager.py +123 -117
  31. reconcile/dynatrace_token_provider/integration.py +1 -1
  32. reconcile/endpoints_discovery/integration.py +4 -1
  33. reconcile/endpoints_discovery/merge_request.py +1 -1
  34. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  35. reconcile/external_resources/factories.py +4 -6
  36. reconcile/external_resources/integration.py +1 -1
  37. reconcile/external_resources/manager.py +8 -6
  38. reconcile/external_resources/meta.py +0 -1
  39. reconcile/external_resources/metrics.py +1 -1
  40. reconcile/external_resources/model.py +19 -15
  41. reconcile/external_resources/reconciler.py +7 -4
  42. reconcile/external_resources/secrets_sync.py +4 -7
  43. reconcile/external_resources/state.py +26 -16
  44. reconcile/fleet_labeler/integration.py +1 -1
  45. reconcile/gabi_authorized_users.py +5 -2
  46. reconcile/gcp_image_mirror.py +2 -2
  47. reconcile/github_org.py +1 -1
  48. reconcile/github_owners.py +4 -0
  49. reconcile/gitlab_housekeeping.py +13 -15
  50. reconcile/gitlab_members.py +6 -12
  51. reconcile/gitlab_owners.py +15 -11
  52. reconcile/gitlab_permissions.py +8 -12
  53. reconcile/glitchtip_project_alerts/integration.py +3 -1
  54. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  55. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  56. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  57. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  58. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  59. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  60. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  61. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  62. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  63. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  64. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  66. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  67. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  68. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  69. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  70. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  71. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  72. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  73. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  74. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  75. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  83. reconcile/gql_definitions/common/apps.py +5 -5
  84. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  85. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  86. reconcile/gql_definitions/common/clusters.py +5 -5
  87. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  88. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  89. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  90. reconcile/gql_definitions/common/github_orgs.py +5 -5
  91. reconcile/gql_definitions/common/jira_settings.py +5 -5
  92. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  93. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  94. reconcile/gql_definitions/common/namespaces.py +5 -5
  95. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  96. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  97. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  98. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  99. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  100. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  101. reconcile/gql_definitions/common/quay_instances.py +5 -5
  102. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  103. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  104. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  105. reconcile/gql_definitions/common/saas_files.py +5 -5
  106. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  107. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  108. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  109. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  110. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  111. reconcile/gql_definitions/common/users.py +5 -5
  112. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  113. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  114. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  115. reconcile/gql_definitions/cost_report/settings.py +5 -5
  116. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  117. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  118. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  119. reconcile/gql_definitions/email_sender/apps.py +5 -5
  120. reconcile/gql_definitions/email_sender/emails.py +5 -5
  121. reconcile/gql_definitions/email_sender/users.py +5 -5
  122. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  123. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  124. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  125. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +33 -6
  126. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  127. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  128. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  129. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  131. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  133. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  135. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  137. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  138. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  139. reconcile/gql_definitions/fragments/disable.py +5 -5
  140. reconcile/gql_definitions/fragments/email_service.py +5 -5
  141. reconcile/gql_definitions/fragments/email_user.py +5 -5
  142. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  143. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  144. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  145. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  146. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  147. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  148. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  150. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  151. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  152. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  153. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  154. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  155. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  156. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  157. reconcile/gql_definitions/fragments/user.py +5 -5
  158. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  159. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  160. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  161. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  162. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  163. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  164. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  165. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  166. reconcile/gql_definitions/integrations/integrations.py +5 -5
  167. reconcile/gql_definitions/introspection.json +724 -129
  168. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  169. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  170. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  171. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  172. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  173. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  174. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  175. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  176. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  177. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  178. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  179. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  180. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  181. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  182. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  183. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  184. reconcile/gql_definitions/rhcs/certs.py +25 -79
  185. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  186. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  187. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  188. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  189. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  190. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  191. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  192. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  193. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  194. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  195. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  196. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  197. reconcile/gql_definitions/status_board/status_board.py +5 -5
  198. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  199. reconcile/gql_definitions/templating/template_collection.py +5 -5
  200. reconcile/gql_definitions/templating/templates.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  204. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  206. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  207. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  208. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  209. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  210. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +30 -6
  211. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  212. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  213. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  214. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  215. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  216. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  217. reconcile/integrations_manager.py +3 -3
  218. reconcile/jenkins_worker_fleets.py +10 -8
  219. reconcile/jira_permissions_validator.py +237 -122
  220. reconcile/ldap_groups/integration.py +1 -1
  221. reconcile/ocm/types.py +35 -57
  222. reconcile/ocm_aws_infrastructure_access.py +1 -1
  223. reconcile/ocm_clusters.py +4 -4
  224. reconcile/ocm_labels/integration.py +3 -2
  225. reconcile/ocm_machine_pools.py +33 -27
  226. reconcile/openshift_base.py +113 -4
  227. reconcile/openshift_cluster_bots.py +1 -1
  228. reconcile/openshift_namespace_labels.py +1 -1
  229. reconcile/openshift_namespaces.py +97 -101
  230. reconcile/openshift_resources_base.py +6 -2
  231. reconcile/openshift_rhcs_certs.py +74 -37
  232. reconcile/openshift_rolebindings.py +7 -11
  233. reconcile/openshift_saas_deploy.py +4 -5
  234. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  235. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  236. reconcile/openshift_serviceaccount_tokens.py +2 -2
  237. reconcile/openshift_upgrade_watcher.py +4 -4
  238. reconcile/oum/labelset.py +5 -3
  239. reconcile/oum/models.py +1 -4
  240. reconcile/prometheus_rules_tester/integration.py +3 -3
  241. reconcile/quay_mirror.py +1 -1
  242. reconcile/queries.py +131 -0
  243. reconcile/rhidp/common.py +3 -5
  244. reconcile/rhidp/sso_client/base.py +16 -5
  245. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  246. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  247. reconcile/skupper_network/integration.py +2 -2
  248. reconcile/slack_usergroups.py +35 -14
  249. reconcile/sql_query.py +1 -0
  250. reconcile/status_board.py +6 -6
  251. reconcile/statuspage/atlassian.py +7 -7
  252. reconcile/statuspage/integrations/maintenances.py +4 -3
  253. reconcile/statuspage/page.py +4 -9
  254. reconcile/statuspage/status.py +5 -8
  255. reconcile/templating/lib/rendering.py +3 -3
  256. reconcile/templating/renderer.py +2 -2
  257. reconcile/terraform_aws_route53.py +7 -1
  258. reconcile/terraform_cloudflare_dns.py +3 -3
  259. reconcile/terraform_cloudflare_resources.py +5 -5
  260. reconcile/terraform_cloudflare_users.py +3 -2
  261. reconcile/terraform_init/integration.py +187 -23
  262. reconcile/terraform_repo.py +16 -12
  263. reconcile/terraform_resources.py +6 -6
  264. reconcile/terraform_tgw_attachments.py +27 -19
  265. reconcile/terraform_users.py +7 -0
  266. reconcile/terraform_vpc_peerings.py +14 -3
  267. reconcile/terraform_vpc_resources/integration.py +10 -1
  268. reconcile/typed_queries/aws_account_tags.py +41 -0
  269. reconcile/typed_queries/cost_report/app_names.py +1 -1
  270. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  271. reconcile/typed_queries/saas_files.py +11 -11
  272. reconcile/typed_queries/status_board.py +2 -2
  273. reconcile/unleash_feature_toggles/integration.py +4 -2
  274. reconcile/utils/acs/base.py +6 -3
  275. reconcile/utils/acs/policies.py +2 -2
  276. reconcile/utils/aws_api.py +51 -20
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/binary.py +7 -12
  282. reconcile/utils/datetime_util.py +67 -0
  283. reconcile/utils/deadmanssnitch_api.py +1 -1
  284. reconcile/utils/differ.py +2 -3
  285. reconcile/utils/early_exit_cache.py +11 -12
  286. reconcile/utils/expiration.py +7 -3
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helpers.py +1 -1
  293. reconcile/utils/instrumented_wrappers.py +1 -1
  294. reconcile/utils/internal_groups/client.py +2 -2
  295. reconcile/utils/internal_groups/models.py +8 -17
  296. reconcile/utils/jinja2/utils.py +6 -101
  297. reconcile/utils/jira_client.py +82 -63
  298. reconcile/utils/jjb_client.py +7 -10
  299. reconcile/utils/jobcontroller/controller.py +2 -2
  300. reconcile/utils/jobcontroller/models.py +17 -1
  301. reconcile/utils/json.py +43 -1
  302. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  303. reconcile/utils/membershipsources/models.py +16 -23
  304. reconcile/utils/membershipsources/resolver.py +4 -2
  305. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  306. reconcile/utils/merge_request_manager/parser.py +6 -6
  307. reconcile/utils/metrics.py +5 -5
  308. reconcile/utils/models.py +304 -82
  309. reconcile/utils/mr/app_interface_reporter.py +2 -2
  310. reconcile/utils/mr/notificator.py +3 -3
  311. reconcile/utils/mr/update_access_report_base.py +3 -4
  312. reconcile/utils/mr/user_maintenance.py +3 -2
  313. reconcile/utils/oc.py +246 -201
  314. reconcile/utils/oc_filters.py +3 -3
  315. reconcile/utils/ocm/addons.py +0 -1
  316. reconcile/utils/ocm/base.py +17 -20
  317. reconcile/utils/ocm/cluster_groups.py +1 -1
  318. reconcile/utils/ocm/identity_providers.py +2 -2
  319. reconcile/utils/ocm/labels.py +1 -1
  320. reconcile/utils/ocm/products.py +8 -8
  321. reconcile/utils/ocm/search_filters.py +3 -6
  322. reconcile/utils/ocm/service_log.py +4 -6
  323. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  324. reconcile/utils/openshift_resource.py +8 -3
  325. reconcile/utils/pagerduty_api.py +10 -7
  326. reconcile/utils/promotion_state.py +6 -11
  327. reconcile/utils/raw_github_api.py +1 -1
  328. reconcile/utils/rhcsv2_certs.py +86 -23
  329. reconcile/utils/rosa/session.py +16 -0
  330. reconcile/utils/runtime/integration.py +2 -3
  331. reconcile/utils/runtime/runner.py +2 -2
  332. reconcile/utils/saasherder/interfaces.py +13 -20
  333. reconcile/utils/saasherder/models.py +23 -20
  334. reconcile/utils/saasherder/saasherder.py +50 -27
  335. reconcile/utils/slack_api.py +2 -2
  336. reconcile/utils/sloth.py +171 -2
  337. reconcile/utils/structs.py +1 -1
  338. reconcile/utils/terraform_client.py +5 -4
  339. reconcile/utils/terrascript_aws_client.py +134 -74
  340. reconcile/utils/unleash/server.py +2 -8
  341. reconcile/utils/vault.py +5 -12
  342. reconcile/utils/vcs.py +8 -8
  343. reconcile/vault_replication.py +107 -42
  344. tools/app_interface_reporter.py +4 -4
  345. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  346. tools/cli_commands/cost_report/view.py +7 -6
  347. tools/cli_commands/erv2.py +1 -1
  348. tools/qontract_cli.py +28 -17
  349. tools/template_validation.py +3 -1
  350. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
  351. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.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,11 +642,9 @@ 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
@@ -650,7 +653,7 @@ class OCCli:
650
653
 
651
654
  @OCDecorators.process_reconcile_time
652
655
  def new_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
653
- if self.is_kind_supported("Project"):
656
+ if self.is_kind_supported(PROJECT_KIND):
654
657
  cmd = ["new-project", namespace]
655
658
  else:
656
659
  cmd = ["create", "namespace", namespace]
@@ -666,7 +669,7 @@ class OCCli:
666
669
 
667
670
  @OCDecorators.process_reconcile_time
668
671
  def delete_project(self, namespace: str) -> OCProcessReconcileTimeDecoratorMsg:
669
- if self.is_kind_supported("Project"):
672
+ if self.is_kind_supported(PROJECT_KIND):
670
673
  cmd = ["delete", "project", namespace]
671
674
  else:
672
675
  cmd = ["delete", "namespace", namespace]
@@ -715,9 +718,9 @@ class OCCli:
715
718
 
716
719
  def sa_get_token(self, namespace: str, name: str) -> str:
717
720
  cmd = ["sa", "-n", namespace, "get-token", name]
718
- return self._run(cmd)
721
+ return self._run(cmd).decode("utf-8")
719
722
 
720
- def get_api_resources(self) -> dict[str, Any]:
723
+ def get_api_resources(self) -> dict[str, list[OCCliApiResource]]:
721
724
  with self.api_resources_lock:
722
725
  if not self.api_resources:
723
726
  cmd = ["api-resources", "--no-headers"]
@@ -921,108 +924,105 @@ class OCCli:
921
924
  if not status["ready"]:
922
925
  raise PodNotReadyError(name)
923
926
 
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:
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:
936
933
  logging.debug([
937
934
  "skipping_pod_recycle_unsupported",
938
935
  self.cluster_name,
939
936
  namespace,
940
- dep_kind,
937
+ resource.kind,
938
+ resource.name,
941
939
  ])
942
- return
940
+ return False
943
941
 
944
- dep_annotations = dep_resource.body["metadata"].get("annotations", {})
945
942
  # 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"')
943
+ annotations = resource.body["metadata"].get("annotations") or {}
944
+ qontract_recycle = annotations.get("qontract.recycle")
950
945
  if qontract_recycle != "true":
951
946
  logging.debug([
952
947
  "skipping_pod_recycle_no_annotation",
953
948
  self.cluster_name,
954
949
  namespace,
955
- dep_kind,
950
+ resource.kind,
951
+ resource.name,
956
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):
957
973
  return
958
974
 
959
- dep_name = dep_resource.name
960
975
  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",
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
+ )
979
984
  ]
985
+
986
+ recycle_names_by_kind = defaultdict(set)
980
987
  for pod in pods_to_recycle:
981
988
  owner = self.get_obj_root_owner(namespace, pod, allow_not_found=True)
982
989
  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)
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
+
1000
1002
  def recycle(
1001
- 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,
1002
1008
  ) -> None:
1003
- """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.
1004
1013
 
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
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
1009
1019
  """
1010
- name = obj["metadata"]["name"]
1011
1020
  logging.info([f"recycle_{kind.lower()}", self.cluster_name, namespace, name])
1012
1021
  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)
1022
+ self._run(
1023
+ ["rollout", "restart", f"{kind}/{name}", "-n", namespace],
1024
+ apply=True,
1025
+ )
1026
1026
 
1027
1027
  def get_obj_root_owner(
1028
1028
  self,
@@ -1064,12 +1064,24 @@ class OCCli:
1064
1064
  )
1065
1065
  return obj
1066
1066
 
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
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
1070
1080
 
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")
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)
1073
1085
  return name in used_resources
1074
1086
 
1075
1087
  @staticmethod
@@ -1078,25 +1090,39 @@ class OCCli:
1078
1090
  kind: str,
1079
1091
  include_optional: bool = True,
1080
1092
  ) -> dict[str, set[str]]:
1081
- if kind not in {"Secret", "ConfigMap"}:
1082
- 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
+
1083
1125
  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
1126
 
1101
1127
  resources: dict[str, set[str]] = {}
1102
1128
  for v in spec.get("volumes") or []:
@@ -1125,8 +1151,8 @@ class OCCli:
1125
1151
  continue
1126
1152
  resource_name = resource_ref[env_ref]
1127
1153
  resources.setdefault(resource_name, set())
1128
- secret_key = resource_ref["key"]
1129
- resources[resource_name].add(secret_key)
1154
+ key = resource_ref["key"]
1155
+ resources[resource_name].add(key)
1130
1156
  except (KeyError, TypeError):
1131
1157
  continue
1132
1158
 
@@ -1187,7 +1213,7 @@ class OCCli:
1187
1213
  def _run_json(
1188
1214
  self, cmd: list[str], allow_not_found: bool = False
1189
1215
  ) -> dict[str, Any]:
1190
- out = self._run(cmd, allow_not_found=allow_not_found)
1216
+ out = self._run(cmd, allow_not_found=allow_not_found).decode("utf-8")
1191
1217
 
1192
1218
  try:
1193
1219
  out_json = json.loads(out)
@@ -1196,76 +1222,90 @@ class OCCli:
1196
1222
 
1197
1223
  return out_json
1198
1224
 
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()
1225
+ def parse_kind(self, kind: str) -> tuple[str, str, str]:
1226
+ """Parse a Kubernetes kind string into its components.
1204
1227
 
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)
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
1232
1260
 
1233
1261
  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()
1262
+ """Returns True if the given kind is supported by the cluster, False otherwise.
1238
1263
 
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
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
1247
1270
 
1248
1271
  def is_kind_namespaced(self, kind: str) -> bool:
1249
- # This is a provisional solution while we work in redefining
1250
- # 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
+
1251
1283
  if not self.api_resources:
1252
- self.get_api_resources()
1284
+ raise RuntimeError("API resources not initialized")
1253
1285
 
1254
- kg = kind.split(".", 1)
1255
- kind = kg[0]
1286
+ kind, group, _ = self.parse_kind(kind)
1256
1287
 
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")
1288
+ if not (resources := self.api_resources.get(kind)):
1289
+ # the kind not found at all
1290
+ raise KindNotFoundError(f"Unsupported resource type: {kind}")
1261
1291
 
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
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}")
1269
1309
 
1270
1310
 
1271
1311
  REQUEST_TIMEOUT = 60
@@ -1305,20 +1345,19 @@ class OCNative(OCCli):
1305
1345
 
1306
1346
  server = connection_parameters.server_url
1307
1347
 
1308
- if server:
1309
- self.client = self._get_client(server, token)
1310
- self.api_resources = self.get_api_resources()
1348
+ if not server:
1349
+ raise Exception("Server name is required!")
1311
1350
 
1312
- else:
1313
- 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()
1314
1356
 
1315
1357
  self.projects = set()
1316
1358
  self.init_projects = init_projects
1317
1359
  if self.init_projects:
1318
- if self.is_kind_supported("Project"):
1319
- kind = "Project.project.openshift.io"
1320
- else:
1321
- kind = "Namespace"
1360
+ kind = PROJECT_KIND if self.is_kind_supported(PROJECT_KIND) else "Namespace"
1322
1361
  self.projects = {p["metadata"]["name"] for p in self.get_all(kind)["items"]}
1323
1362
 
1324
1363
  def __enter__(self) -> OCNative:
@@ -1368,8 +1407,10 @@ class OCNative(OCCli):
1368
1407
 
1369
1408
  @retry(max_attempts=5, exceptions=(ServerTimeoutError))
1370
1409
  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)
1410
+ resource = self.get_api_resource(kind)
1411
+ obj_client = self._get_obj_client(
1412
+ group_version=resource.group_version, kind=resource.kind
1413
+ )
1373
1414
 
1374
1415
  namespace = ""
1375
1416
  if "namespace" in kwargs:
@@ -1421,8 +1462,10 @@ class OCNative(OCCli):
1421
1462
  name: str | None = None,
1422
1463
  allow_not_found: bool = False,
1423
1464
  ) -> dict[str, Any]:
1424
- k, group_version = self._parse_kind(kind)
1425
- 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
+ )
1426
1469
  try:
1427
1470
  obj = obj_client.get(
1428
1471
  name=name,
@@ -1436,8 +1479,10 @@ class OCNative(OCCli):
1436
1479
  raise StatusCodeError(f"[{self.server}]: {e}") from None
1437
1480
 
1438
1481
  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)
1482
+ resource = self.get_api_resource(kind)
1483
+ obj_client = self._get_obj_client(
1484
+ group_version=resource.group_version, kind=resource.kind
1485
+ )
1441
1486
  try:
1442
1487
  return obj_client.get(_request_timeout=REQUEST_TIMEOUT).to_dict()
1443
1488
  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,
@@ -188,7 +188,6 @@ class AddonServiceV2(AddonService):
188
188
  next_run=policy.get("next_run"),
189
189
  version=policy["version"],
190
190
  state=policy.get("state"),
191
- addon_service=self,
192
191
  )
193
192
  )
194
193