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

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

Potentially problematic release.


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

Files changed (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -17,10 +17,8 @@ from jira.resources import CustomFieldOption as JiraCustomFieldOption
17
17
  from jira.resources import Resource
18
18
  from pydantic import BaseModel
19
19
 
20
- from reconcile.utils.secret_reader import SecretReader
21
-
22
20
  if TYPE_CHECKING:
23
- from collections.abc import Iterable, Mapping
21
+ from collections.abc import Iterable
24
22
 
25
23
 
26
24
  class JiraWatcherSettings(Protocol):
@@ -87,36 +85,18 @@ class IssueField(BaseModel):
87
85
  options: list[FieldOption | CustomFieldOption]
88
86
 
89
87
 
88
+ CREATE_ISSUES = "CREATE_ISSUES"
89
+ TRANSITION_ISSUES = "TRANSITION_ISSUES"
90
+ PERMISSIONS = [CREATE_ISSUES, TRANSITION_ISSUES]
91
+
92
+
90
93
  class JiraClient:
91
94
  """Wrapper around Jira client."""
92
95
 
93
96
  DEFAULT_CONNECT_TIMEOUT = 60
94
97
  DEFAULT_READ_TIMEOUT = 60
95
98
 
96
- def __init__(
97
- self,
98
- jira_board: Mapping[str, Any] | None = None,
99
- settings: Mapping | None = None,
100
- jira_api: JIRA | None = None,
101
- project: str | None = None,
102
- server: str | None = None,
103
- ):
104
- """
105
- Note: jira_board and settings is to be deprecated. Use JiraClient.create() instead.
106
- """
107
- if jira_api and jira_board:
108
- raise RuntimeError(
109
- "jira_board parameter is deprecated. Use JiraClient.create() instead."
110
- )
111
- if not (jira_api and project):
112
- # kept for backwards-compatibility
113
- if not jira_board:
114
- raise RuntimeError(
115
- "JiraClient needs jira_api and project or jira_board."
116
- )
117
- self._deprecated_init(jira_board=jira_board, settings=settings)
118
- return
119
-
99
+ def __init__(self, jira_api: JIRA, project: str, server: str):
120
100
  self.server = server
121
101
  self.project = project
122
102
  self.jira = jira_api
@@ -128,47 +108,31 @@ class JiraClient:
128
108
  self.project_issue_types = functools.cache(self._project_issue_types)
129
109
  self.project_issue_fields = functools.cache(self._project_issue_fields)
130
110
 
131
- def _deprecated_init(
132
- self, jira_board: Mapping[str, Any], settings: Mapping | None
133
- ) -> None:
134
- secret_reader = SecretReader(settings=settings)
135
- self.project = jira_board["name"]
136
- jira_server = jira_board["server"]
137
- self.server = jira_server["serverUrl"]
138
- token = jira_server["token"]
139
- token_auth = secret_reader.read(token)
140
- read_timeout = 60
141
- connect_timeout = 60
142
- if settings and settings["jiraWatcher"]:
143
- read_timeout = settings["jiraWatcher"]["readTimeout"]
144
- connect_timeout = settings["jiraWatcher"]["connectTimeout"]
145
- if not self.server:
146
- raise RuntimeError("JiraClient.server is not set.")
147
-
148
- self.jira = JIRA(
149
- self.server,
150
- token_auth=token_auth,
151
- timeout=(read_timeout, connect_timeout),
152
- logging=False,
153
- )
154
-
155
111
  @staticmethod
156
112
  def create(
157
113
  project_name: str,
158
114
  token: str,
115
+ email: str | None,
159
116
  server_url: str,
160
117
  jira_watcher_settings: JiraWatcherSettings | None = None,
161
118
  ) -> JiraClient:
119
+ """Create a Jira client for the given project."""
162
120
  read_timeout = JiraClient.DEFAULT_READ_TIMEOUT
163
121
  connect_timeout = JiraClient.DEFAULT_CONNECT_TIMEOUT
164
122
  if jira_watcher_settings:
165
123
  read_timeout = jira_watcher_settings.read_timeout
166
124
  connect_timeout = jira_watcher_settings.connect_timeout
125
+
126
+ # Jira Cloud uses email+API token for basic auth
127
+ # Jira Server/Data Center can use token auth (personal access token)
128
+ auth_params: dict[str, Any] = (
129
+ {"basic_auth": (email, token)} if email else {"token_auth": token}
130
+ )
167
131
  jira_api = JIRA(
168
132
  server=server_url,
169
- token_auth=token,
170
133
  timeout=(read_timeout, connect_timeout),
171
134
  logging=False,
135
+ **auth_params,
172
136
  )
173
137
  return JiraClient(
174
138
  jira_api=jira_api,
@@ -176,7 +140,13 @@ class JiraClient:
176
140
  server=server_url,
177
141
  )
178
142
 
143
+ @property
144
+ def is_cloud(self) -> bool:
145
+ """Return whether we are on a Cloud based Jira instance."""
146
+ return self.jira.deploymentType == "Cloud"
147
+
179
148
  def get_issues(self, fields: Iterable | None = None) -> list[Issue]:
149
+ """Return all issues for our project."""
180
150
  block_size = 100
181
151
  block_num = 0
182
152
 
@@ -227,20 +197,34 @@ class JiraClient:
227
197
  return issue
228
198
 
229
199
  def _my_permissions(self, project: str) -> dict[str, Any]:
200
+ """Return my permissions for the given project.
201
+
202
+ Don't use this function directly, use self.my_permissions which is cached."""
203
+ if self.is_cloud:
204
+ return self.jira.my_permissions(
205
+ projectKey=project, permissions=",".join(PERMISSIONS)
206
+ )["permissions"]
230
207
  return self.jira.my_permissions(projectKey=project)["permissions"]
231
208
 
232
209
  def can_i(self, permission: str) -> bool:
210
+ """Return whether I have the given permission in the project."""
233
211
  return bool(
234
212
  self.my_permissions(project=self.project)[permission]["havePermission"]
235
213
  )
236
214
 
237
215
  def can_create_issues(self) -> bool:
238
- return self.can_i("CREATE_ISSUES")
216
+ """Return whether I can create issues in the project."""
217
+ return self.can_i(CREATE_ISSUES)
239
218
 
240
219
  def can_transition_issues(self) -> bool:
241
- return self.can_i("TRANSITION_ISSUES")
220
+ """Return whether I can transition issues in the project."""
221
+ return self.can_i(TRANSITION_ISSUES)
242
222
 
243
223
  def _project_issue_types(self, project: str) -> list[IssueType]:
224
+ """Return all available issue types (e.g. Task, Bug) for the project.
225
+
226
+ Don't use this function directly, use self.project_issue_types which is cached.
227
+ """
244
228
  # Don't use self.project here, because of function.cache usage
245
229
  return [
246
230
  IssueType(id=t.id, name=t.name, statuses=[s.name for s in t.statuses])
@@ -248,6 +232,7 @@ class JiraClient:
248
232
  ]
249
233
 
250
234
  def get_issue_type(self, issue_type: str) -> IssueType | None:
235
+ """Return a issue type (e.g. Task) for the project if it exists."""
251
236
  for _issue_type in self.project_issue_types(self.project):
252
237
  if _issue_type.name == issue_type:
253
238
  return _issue_type
@@ -255,15 +240,23 @@ class JiraClient:
255
240
 
256
241
  @staticmethod
257
242
  def _get_allowed_issue_field_options(
258
- allowed_values: list[Resource],
243
+ allowed_values: list[Resource] | list[dict[str, str]],
259
244
  ) -> list[FieldOption | CustomFieldOption]:
260
- """Return a list of allowed values for a field."""
261
- return [
262
- CustomFieldOption(value=v.value)
263
- if isinstance(v, JiraCustomFieldOption)
264
- else FieldOption(name=v.name)
265
- for v in allowed_values
266
- ]
245
+ """Return a list of allowed values for a field. E.g. Minor, Major ... for Priority in a Task."""
246
+ items: list[FieldOption | CustomFieldOption] = []
247
+ for v in allowed_values:
248
+ match v:
249
+ case dict() if "value" in v:
250
+ items.append(CustomFieldOption(value=v["value"]))
251
+ case dict() if "name" in v:
252
+ items.append(FieldOption(name=v["name"]))
253
+ case JiraCustomFieldOption():
254
+ items.append(CustomFieldOption(value=v.value))
255
+ case Resource():
256
+ items.append(FieldOption(name=v.name))
257
+ case _:
258
+ logging.warning(f"Unknown allowed value type: {type(v)}")
259
+ return items
267
260
 
268
261
  def _project_issue_fields(
269
262
  self, project: str, issue_type_id: str
@@ -273,6 +266,27 @@ class JiraClient:
273
266
  This API endpoint needs createIssue project permissions.
274
267
  """
275
268
  # Don't use self.project here, because of function.cache usage
269
+ if self.is_cloud:
270
+ metadata = self.jira.createmeta(
271
+ projectKeys=self.project,
272
+ issuetypeIds=[issue_type_id],
273
+ expand="projects.issuetypes.fields",
274
+ )
275
+ if not metadata["projects"] or not metadata["projects"][0]["issuetypes"]:
276
+ return []
277
+ return [
278
+ IssueField(
279
+ name=field["name"],
280
+ id=field_id,
281
+ options=self._get_allowed_issue_field_options(
282
+ field.get("allowedValues", [])
283
+ ),
284
+ )
285
+ for field_id, field in metadata["projects"][0]["issuetypes"][0][
286
+ "fields"
287
+ ].items()
288
+ ]
289
+
276
290
  return [
277
291
  IssueField(
278
292
  name=field.name,
@@ -304,6 +318,10 @@ class JiraClient:
304
318
 
305
319
  def project_priority_scheme(self) -> list[str]:
306
320
  """Return a list of all priority IDs for the project."""
321
+ if self.is_cloud:
322
+ # Cloud does not have a way to retrieve project specific priority schemes
323
+ return []
324
+
307
325
  scheme = self.jira.project_priority_scheme(self.project)
308
326
  return scheme.optionIds
309
327
 
@@ -329,4 +347,5 @@ class JiraClient:
329
347
 
330
348
  @property
331
349
  def is_archived(self) -> bool:
332
- return self.jira.project(self.project).archived
350
+ """Return whether the project is archived."""
351
+ return getattr(self.jira.project(self.project), "archived", False)
@@ -1,6 +1,5 @@
1
1
  import difflib
2
2
  import filecmp
3
- import json
4
3
  import logging
5
4
  import os
6
5
  import re
@@ -8,7 +7,9 @@ import shutil
8
7
  import subprocess
9
8
  import tempfile
10
9
  import xml.etree.ElementTree as ET
10
+ from collections.abc import Iterable, Mapping
11
11
  from os import path
12
+ from pathlib import Path
12
13
  from subprocess import (
13
14
  PIPE,
14
15
  STDOUT,
@@ -17,30 +18,44 @@ from subprocess import (
17
18
  from typing import Any
18
19
 
19
20
  import yaml
20
- from jenkins_jobs.builder import JenkinsManager
21
21
  from jenkins_jobs.errors import JenkinsJobsException
22
- from jenkins_jobs.parser import YamlParser
23
- from jenkins_jobs.registry import ModuleRegistry
22
+ from jenkins_jobs.loader import load_files
23
+ from jenkins_jobs.roots import Roots
24
24
  from sretoolbox.utils import retry
25
25
 
26
26
  from reconcile.utils import throughput
27
27
  from reconcile.utils.helpers import toggle_logger
28
+ from reconcile.utils.json import json_dumps
29
+ from reconcile.utils.secret_reader import SecretReaderBase
30
+ from reconcile.utils.state import State
28
31
  from reconcile.utils.vcs import GITHUB_BASE_URL
29
32
 
30
33
  JJB_INI = "[jenkins]\nurl = https://JENKINS_URL"
31
34
 
32
35
 
36
+ class MissingJobUrlError(Exception):
37
+ pass
38
+
39
+
33
40
  class JJB:
34
41
  """Wrapper around Jenkins Jobs"""
35
42
 
36
- def __init__(self, configs, ssl_verify=True, secret_reader=None, print_only=False):
43
+ def __init__(
44
+ self,
45
+ configs: list[dict[str, Any]],
46
+ ssl_verify: bool = True,
47
+ secret_reader: SecretReaderBase | None = None,
48
+ print_only: bool = False,
49
+ ) -> None:
37
50
  self.print_only = print_only
38
51
  self.secret_reader = secret_reader
52
+ if not self.print_only and self.secret_reader is None:
53
+ raise ValueError("secret_reader must be provided if print_only is False")
39
54
  self.collect_configs(configs)
40
55
  self.modify_logger()
41
56
  self.python_https_verify = str(int(ssl_verify))
42
57
 
43
- def collect_configs(self, configs):
58
+ def collect_configs(self, configs: list[dict[str, Any]]) -> None:
44
59
  instances = {
45
60
  c["instance"]["name"]: {
46
61
  "serverUrl": c["instance"]["serverUrl"],
@@ -57,7 +72,7 @@ class JJB:
57
72
  server_url = data["serverUrl"]
58
73
  wd = tempfile.mkdtemp()
59
74
  ini = JJB_INI
60
- if not self.print_only:
75
+ if not self.print_only and self.secret_reader:
61
76
  ini = self.secret_reader.read(token)
62
77
  ini = ini.replace('"', "")
63
78
  ini = ini.replace("false", "False")
@@ -92,7 +107,7 @@ class JJB:
92
107
  self.instance_urls = instance_urls
93
108
  self.working_dirs = working_dirs
94
109
 
95
- def overwrite_configs(self, configs):
110
+ def overwrite_configs(self, configs: Mapping[str, str] | State) -> None:
96
111
  """This function will override the existing
97
112
  config files in the working directories with
98
113
  the supplied configs"""
@@ -101,12 +116,12 @@ class JJB:
101
116
  with open(config_path, "w", encoding="locale") as f:
102
117
  f.write(configs[name])
103
118
 
104
- def sort(self, configs):
119
+ def sort(self, configs: list[dict[str, Any]]) -> None:
105
120
  configs.sort(key=self.sort_by_name)
106
- configs.sort(key=self.sort_by_type)
121
+ configs.sort(key=lambda x: self.sort_by_type(x) or 0)
107
122
 
108
123
  @staticmethod
109
- def sort_by_type(config):
124
+ def sort_by_type(config: Mapping[str, Any]) -> int:
110
125
  if config["type"] == "defaults":
111
126
  return 0
112
127
  if config["type"] == "global-defaults":
@@ -123,12 +138,13 @@ class JJB:
123
138
  return 40
124
139
  if config["type"] == "jobs":
125
140
  return 50
141
+ return 100
126
142
 
127
143
  @staticmethod
128
- def sort_by_name(config):
144
+ def sort_by_name(config: Mapping[str, Any]) -> str:
129
145
  return config["name"]
130
146
 
131
- def get_configs(self):
147
+ def get_configs(self) -> dict[str, str]:
132
148
  """This function gets the configs from the
133
149
  working directories"""
134
150
  configs = {}
@@ -139,7 +155,7 @@ class JJB:
139
155
 
140
156
  return configs
141
157
 
142
- def generate(self, io_dir, fetch_state):
158
+ def generate(self, io_dir: str, fetch_state: str) -> None:
143
159
  """
144
160
  Generates job definitions from JJB configs
145
161
 
@@ -163,7 +179,7 @@ class JJB:
163
179
  self.execute(args)
164
180
  throughput.change_files_ownership(io_dir)
165
181
 
166
- def print_diffs(self, io_dir, instance_name=None):
182
+ def print_diffs(self, io_dir: str, instance_name: str | None = None) -> None:
167
183
  """Print the diffs between the current and
168
184
  the desired job definitions"""
169
185
  current_path = path.join(io_dir, "jjb", "current")
@@ -179,7 +195,7 @@ class JJB:
179
195
  self.print_diff(delete, current_path, "delete")
180
196
  self.print_diff(common, desired_path, "update")
181
197
 
182
- def print_diff(self, files, replace_path, action):
198
+ def print_diff(self, files: Iterable[str], replace_path: str, action: str) -> None:
183
199
  for f in files:
184
200
  if action == "update":
185
201
  ft = self.toggle_cd(f)
@@ -210,11 +226,16 @@ class JJB:
210
226
  ]
211
227
  logging.debug("DIFF:\n" + "".join(diff))
212
228
 
213
- def compare_files(self, from_files, subtract_files, in_op=False):
229
+ def compare_files(
230
+ self,
231
+ from_files: Iterable[str],
232
+ subtract_files: Iterable[str],
233
+ in_op: bool = False,
234
+ ) -> list[str]:
214
235
  return [f for f in from_files if (self.toggle_cd(f) in subtract_files) is in_op]
215
236
 
216
237
  @staticmethod
217
- def get_files(search_path, instance_name=None):
238
+ def get_files(search_path: str, instance_name: str | None = None) -> list[str]:
218
239
  if instance_name is not None:
219
240
  search_path = path.join(search_path, instance_name)
220
241
  return [
@@ -222,7 +243,7 @@ class JJB:
222
243
  ]
223
244
 
224
245
  @staticmethod
225
- def toggle_cd(file_name):
246
+ def toggle_cd(file_name: str) -> str:
226
247
  if "desired" in file_name:
227
248
  return file_name.replace("desired", "current")
228
249
  return file_name.replace("current", "desired")
@@ -248,43 +269,40 @@ class JJB:
248
269
  raise
249
270
 
250
271
  @staticmethod
251
- def get_jjb(args):
272
+ def get_jjb(args: Iterable[str]) -> Any:
252
273
  from jenkins_jobs.cli.entry import JenkinsJobs # noqa: PLC0415
253
274
 
254
275
  return JenkinsJobs(args)
255
276
 
256
- def execute(self, args):
277
+ def execute(self, args: Iterable[str]) -> None:
257
278
  jjb = self.get_jjb(args)
258
279
  with toggle_logger():
259
280
  jjb.execute()
260
281
 
261
- def modify_logger(self):
282
+ def modify_logger(self) -> None:
262
283
  yaml.warnings({"YAMLLoadWarning": False})
263
284
  formatter = logging.Formatter("%(levelname)s: %(message)s")
264
285
  logger = logging.getLogger()
265
286
  logger.handlers[0].setFormatter(formatter)
266
287
 
267
- def cleanup(self):
288
+ def cleanup(self) -> None:
268
289
  for wd in self.working_dirs.values():
269
290
  shutil.rmtree(wd)
270
291
 
271
292
  @retry(exceptions=(JenkinsJobsException))
272
- def get_jobs(self, wd, name):
293
+ def get_jobs(self, wd: str, name: str) -> list[dict[str, Any]]:
273
294
  ini_path = f"{wd}/{name}.ini"
274
295
  config_path = f"{wd}/config.yaml"
275
296
 
276
297
  args = ["--conf", ini_path, "test", config_path]
277
298
  jjb = self.get_jjb(args)
278
- builder = JenkinsManager(jjb.jjb_config)
279
- registry = ModuleRegistry(jjb.jjb_config, builder.plugins_list)
280
- parser = YamlParser(jjb.jjb_config)
281
- parser.load_files(jjb.options.path)
282
- jobs, _ = parser.expandYaml(registry, jjb.options.names)
299
+ roots = Roots(jjb.jjb_config)
300
+ load_files(jjb.jjb_config, roots, [Path(config_path)])
301
+ job_view_data_list = roots.generate_jobs()
302
+ return [job.data for job in job_view_data_list]
283
303
 
284
- return jobs
285
-
286
- def get_job_webhooks_data(self):
287
- job_webhooks_data = {}
304
+ def get_job_webhooks_data(self) -> dict[str, list[dict[str, Any]]]:
305
+ job_webhooks_data: dict[str, list[dict[str, Any]]] = {}
288
306
  for name, wd in self.working_dirs.items():
289
307
  jobs = self.get_jobs(wd, name)
290
308
 
@@ -313,7 +331,7 @@ class JJB:
313
331
 
314
332
  return job_webhooks_data
315
333
 
316
- def get_repos(self):
334
+ def get_repos(self) -> set[str]:
317
335
  repos = set()
318
336
  for name, wd in self.working_dirs.items():
319
337
  jobs = self.get_jobs(wd, name)
@@ -321,11 +339,11 @@ class JJB:
321
339
  job_name = job["name"]
322
340
  try:
323
341
  repos.add(self.get_repo_url(job))
324
- except KeyError:
342
+ except MissingJobUrlError:
325
343
  logging.debug(f"missing github url: {job_name}")
326
344
  return repos
327
345
 
328
- def get_admins(self):
346
+ def get_admins(self) -> set[str]:
329
347
  admins = set()
330
348
  for name, wd in self.working_dirs.items():
331
349
  jobs = self.get_jobs(wd, name)
@@ -340,15 +358,29 @@ class JJB:
340
358
  return admins
341
359
 
342
360
  @staticmethod
343
- def get_repo_url(job):
344
- repo_url_raw = job["properties"][0]["github"]["url"]
361
+ def get_repo_url(job: Mapping[str, Any]) -> str:
362
+ repo_url_raw = job.get("properties", [{}])[0].get("github", {}).get("url")
363
+
364
+ # we may be in a Github Branch Source type of job
365
+ if not repo_url_raw:
366
+ gh_org = job.get("scm", [{}])[0].get("github", {}).get("repo-owner")
367
+ gh_repo = job.get("scm", [{}])[0].get("github", {}).get("repo")
368
+ if gh_org and gh_repo:
369
+ repo_url_raw = f"https://github.com/{gh_org}/{gh_repo}/"
370
+ else:
371
+ raise MissingJobUrlError(
372
+ f"Cannot find job url for {job['display-name']}"
373
+ )
374
+
345
375
  return repo_url_raw.strip("/").replace(".git", "")
346
376
 
347
377
  @staticmethod
348
- def get_ref(job: dict) -> str:
378
+ def get_ref(job: Mapping[str, Any]) -> str:
349
379
  return job["scm"][0]["git"]["branches"][0]
350
380
 
351
- def get_all_jobs(self, job_types=None, instance_name=None) -> dict[str, list[dict]]:
381
+ def get_all_jobs(
382
+ self, job_types: Iterable[str] | None = None, instance_name: str | None = None
383
+ ) -> dict[str, list[dict[str, Any]]]:
352
384
  if job_types is None:
353
385
  job_types = []
354
386
  all_jobs: dict[str, list[dict]] = {}
@@ -366,8 +398,8 @@ class JJB:
366
398
 
367
399
  return all_jobs
368
400
 
369
- def print_jobs(self, job_name=None):
370
- all_jobs = {}
401
+ def print_jobs(self, job_name: str | None = None) -> None:
402
+ all_jobs: dict[str, list[dict[str, Any]]] = {}
371
403
  found = False
372
404
  for name, wd in self.working_dirs.items():
373
405
  logging.debug(f"getting jobs from {name}")
@@ -380,7 +412,7 @@ class JJB:
380
412
  found = True
381
413
  if not found:
382
414
  raise ValueError(f"job name {job_name} is not found")
383
- print(json.dumps(all_jobs, indent=2))
415
+ print(json_dumps(all_jobs, indent=2))
384
416
 
385
417
  def get_job_by_repo_url(self, repo_url: str, job_type: str) -> dict[str, Any]:
386
418
  for jobs in self.get_all_jobs(job_types=[job_type]).values():
@@ -388,13 +420,13 @@ class JJB:
388
420
  try:
389
421
  if self.get_repo_url(job).lower() == repo_url.rstrip("/").lower():
390
422
  return job
391
- except KeyError:
423
+ except MissingJobUrlError:
392
424
  # something wrong here. ignore this job
393
425
  pass
394
426
  raise ValueError(f"job with {job_type=} and {repo_url=} not found")
395
427
 
396
428
  @staticmethod
397
- def get_trigger_phrases_regex(job: dict) -> str | None:
429
+ def get_trigger_phrases_regex(job: Mapping[str, Any]) -> str | None:
398
430
  for trigger in job["triggers"]:
399
431
  if "gitlab" in trigger:
400
432
  return trigger["gitlab"].get("note-regex")
@@ -403,7 +435,7 @@ class JJB:
403
435
  return None
404
436
 
405
437
  @staticmethod
406
- def get_gitlab_webhook_trigger(job: dict) -> list[str]:
438
+ def get_gitlab_webhook_trigger(job: Mapping[str, Any]) -> list[str]:
407
439
  gitlab_triggers = job["triggers"][0]["gitlab"]
408
440
  # pr-check job should be triggered by merge request events
409
441
  # and certain comments: [test]|/retest|/lgtm|/lgtm cancel|/hold|/hold cancel
@@ -3,7 +3,7 @@ import time
3
3
  from datetime import datetime
4
4
  from typing import Protocol, TextIO
5
5
 
6
- from kubernetes.client import ( # type: ignore[attr-defined]
6
+ from kubernetes.client import (
7
7
  ApiClient,
8
8
  V1Job,
9
9
  V1ObjectMeta,
@@ -100,7 +100,7 @@ class K8sJobController:
100
100
  """
101
101
  new_cache = {}
102
102
  for item in self.oc.get_items(
103
- kind="Job",
103
+ kind="Job.batch",
104
104
  namespace=self.namespace,
105
105
  ):
106
106
  openshift_resource = OpenshiftResource(
@@ -38,6 +38,8 @@ class JobValidationError(Exception):
38
38
 
39
39
 
40
40
  JOB_GENERATION_ANNOTATION = "qontract-reconcile/job.generation"
41
+ MAX_JOB_NAME_LENGTH = 63
42
+ UNIT_OF_WORK_DIGEST_LENGTH = 10
41
43
 
42
44
 
43
45
  class K8sJob(ABC):
@@ -72,7 +74,21 @@ class K8sJob(ABC):
72
74
  """
73
75
 
74
76
  def name(self) -> str:
75
- return f"{self.name_prefix()}-{self.unit_of_work_digest()}"
77
+ """
78
+ Generate the full job name by combining the name prefix with a digest.
79
+
80
+ The name is constructed from the name_prefix (truncated to ensure total
81
+ length compliance) and the unit_of_work_digest. The total length is
82
+ limited to MAX_JOB_NAME_LENGTH (63 characters) to comply with Kubernetes
83
+ naming constraints.
84
+
85
+ Returns:
86
+ A unique job name in the format: {name_prefix}-{digest}
87
+ """
88
+ prefix = self.name_prefix()[
89
+ : MAX_JOB_NAME_LENGTH - UNIT_OF_WORK_DIGEST_LENGTH - 1
90
+ ]
91
+ return f"{prefix}-{self.unit_of_work_digest(UNIT_OF_WORK_DIGEST_LENGTH)}"
76
92
 
77
93
  @abstractmethod
78
94
  def name_prefix(self) -> str: