qontract-reconcile 0.10.2.dev299__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 (403) hide show
  1. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +399 -394
  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 +4 -4
  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 +8 -11
  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 +23 -10
  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 +492 -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 +10 -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 +3050 -1393
  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 +448 -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 +5 -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 +4 -0
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  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 +27 -19
  282. reconcile/terraform_users.py +29 -21
  283. reconcile/terraform_vpc_peerings.py +16 -4
  284. reconcile/terraform_vpc_resources/integration.py +32 -2
  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 +59 -43
  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 +27 -20
  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 +4 -4
  380. reconcile/utils/terraform_client.py +32 -29
  381. reconcile/utils/terrascript_aws_client.py +658 -480
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/throughput.py +1 -1
  384. reconcile/utils/unleash/server.py +2 -8
  385. reconcile/utils/vault.py +44 -41
  386. reconcile/utils/vcs.py +8 -8
  387. reconcile/vault_replication.py +119 -58
  388. reconcile/vpc_peerings_validator.py +2 -2
  389. tools/app_interface_reporter.py +4 -4
  390. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  391. tools/cli_commands/cost_report/view.py +7 -6
  392. tools/cli_commands/erv2.py +1 -1
  393. tools/cli_commands/gpg_encrypt.py +4 -1
  394. tools/cli_commands/systems_and_tools.py +5 -1
  395. tools/qontract_cli.py +36 -21
  396. tools/sre_checkpoints/util.py +5 -3
  397. tools/template_validation.py +3 -1
  398. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  399. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  400. reconcile/jenkins/__init__.py +0 -0
  401. reconcile/jenkins/types.py +0 -77
  402. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
  403. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
@@ -164,7 +164,9 @@ class GlitchtipClient(ApiBase):
164
164
  return ProjectAlert(
165
165
  **self._post(
166
166
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/",
167
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
167
+ data=alert.model_dump(
168
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
169
+ ),
168
170
  )
169
171
  )
170
172
 
@@ -183,7 +185,9 @@ class GlitchtipClient(ApiBase):
183
185
  return ProjectAlert(
184
186
  **self._put(
185
187
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/{alert.pk}/",
186
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
188
+ data=alert.model_dump(
189
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
190
+ ),
187
191
  )
188
192
  )
189
193
 
@@ -3,13 +3,13 @@ from __future__ import annotations
3
3
  import re
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, Self
7
7
 
8
8
  from pydantic import (
9
9
  BaseModel,
10
10
  Field,
11
- root_validator,
12
- validator,
11
+ field_validator,
12
+ model_validator,
13
13
  )
14
14
 
15
15
  if TYPE_CHECKING:
@@ -49,7 +49,8 @@ class Team(BaseModel):
49
49
  slug: str = ""
50
50
  users: list[User] = []
51
51
 
52
- @root_validator(pre=True)
52
+ @model_validator(mode="before")
53
+ @classmethod
53
54
  def name_xor_slug_must_be_set(
54
55
  cls, values: MutableMapping[str, Any]
55
56
  ) -> MutableMapping[str, Any]:
@@ -58,11 +59,11 @@ class Team(BaseModel):
58
59
  ), "name xor slug must be set!"
59
60
  return values
60
61
 
61
- @root_validator
62
- def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
63
- values["slug"] = values.get("slug") or slugify(values.get("name", ""))
64
- values["name"] = slugify(values.get("name", "")) or values.get("slug")
65
- return values
62
+ @model_validator(mode="after")
63
+ def slugify(self) -> Self:
64
+ self.slug = self.slug or slugify(self.name)
65
+ self.name = slugify(self.name) or self.slug
66
+ return self
66
67
 
67
68
  def __lt__(self, other: Team) -> bool:
68
69
  return self.slug < other.slug
@@ -86,16 +87,15 @@ class RecipientType(Enum):
86
87
  WEBHOOK = "webhook"
87
88
 
88
89
 
89
- class ProjectAlertRecipient(BaseModel):
90
+ class ProjectAlertRecipient(
91
+ BaseModel, validate_by_name=True, validate_by_alias=True, use_enum_values=True
92
+ ):
90
93
  pk: int | None = Field(None, alias="id")
91
94
  recipient_type: RecipientType = Field(..., alias="recipientType")
92
95
  url: str = ""
93
96
 
94
- class Config:
95
- allow_population_by_field_name = True
96
- use_enum_values = True
97
-
98
- @validator("recipient_type")
97
+ @field_validator("recipient_type")
98
+ @classmethod
99
99
  def recipient_type_enforce_enum_type(cls, v: str | RecipientType) -> RecipientType:
100
100
  if isinstance(v, RecipientType):
101
101
  return v
@@ -113,17 +113,15 @@ class ProjectAlertRecipient(BaseModel):
113
113
  return hash((self.recipient_type, self.url))
114
114
 
115
115
 
116
- class ProjectAlert(BaseModel):
116
+ class ProjectAlert(BaseModel, validate_by_name=True, validate_by_alias=True):
117
117
  pk: int | None = Field(None, alias="id")
118
118
  name: str
119
119
  timespan_minutes: int = Field(..., alias="timespanMinutes")
120
120
  quantity: int
121
121
  recipients: list[ProjectAlertRecipient] = Field([], alias="alertRecipients")
122
122
 
123
- class Config:
124
- allow_population_by_field_name = True
125
-
126
- @root_validator
123
+ @model_validator(mode="before")
124
+ @classmethod
127
125
  def empty_name(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
128
126
  # name is an empty string if the alert was created manually because it can't be set via UI
129
127
  # use the pk instead.
@@ -141,20 +139,18 @@ class ProjectAlert(BaseModel):
141
139
  )
142
140
 
143
141
 
144
- class Project(BaseModel):
142
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
145
143
  pk: int | None = Field(None, alias="id")
146
144
  name: str
147
145
  slug: str = ""
148
- platform: str | None
146
+ platform: str | None = None
149
147
  teams: list[Team] = []
150
148
  alerts: list[ProjectAlert] = []
151
149
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
152
150
  organization: Organization | None = None
153
151
 
154
- class Config:
155
- allow_population_by_field_name = True
156
-
157
- @root_validator
152
+ @model_validator(mode="before")
153
+ @classmethod
158
154
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
159
155
  values["slug"] = values.get("slug") or slugify(values["name"])
160
156
  return values
@@ -195,7 +191,8 @@ class Organization(BaseModel):
195
191
  teams: list[Team] = []
196
192
  users: list[User] = []
197
193
 
198
- @root_validator
194
+ @model_validator(mode="before")
195
+ @classmethod
199
196
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
200
197
  values["slug"] = values.get("slug") or slugify(values["name"])
201
198
  return values
@@ -212,4 +209,4 @@ class Organization(BaseModel):
212
209
  return hash(self.name)
213
210
 
214
211
 
215
- Project.update_forward_refs()
212
+ Project.model_rebuild()
reconcile/utils/gpg.py CHANGED
@@ -8,7 +8,7 @@ from subprocess import (
8
8
  )
9
9
 
10
10
 
11
- def gpg_encrypt(content, public_gpg_key):
11
+ def gpg_encrypt(content: str, public_gpg_key: str) -> str:
12
12
  public_gpg_key_dec = base64.b64decode(public_gpg_key)
13
13
 
14
14
  with tempfile.TemporaryDirectory() as gnupg_home_dir:
@@ -22,6 +22,8 @@ def gpg_encrypt(content, public_gpg_key):
22
22
  )
23
23
  out = proc.stdout.decode("utf-8")
24
24
  match = re.search(r"<\S+>", out)
25
+ if not match:
26
+ raise ValueError("No recipient found in GPG import output")
25
27
  recipient = match.group(0)[1:-1]
26
28
  # encrypt content
27
29
  proc = run(
@@ -41,5 +43,5 @@ def gpg_encrypt(content, public_gpg_key):
41
43
  stderr=STDOUT,
42
44
  check=True,
43
45
  )
44
- out = proc.stdout
45
- return out.decode("utf-8")
46
+ encrypted_out = proc.stdout
47
+ return encrypted_out.decode("utf-8")
reconcile/utils/gql.py CHANGED
@@ -109,7 +109,7 @@ class GqlApi:
109
109
  if int_name:
110
110
  integrations = self.query(INTEGRATIONS_QUERY, skip_validation=True)
111
111
 
112
- for integration in integrations["integrations"]:
112
+ for integration in integrations["integrations"] if integrations else []:
113
113
  if integration["name"] == int_name:
114
114
  self._valid_schemas = integration["schemas"]
115
115
  break
@@ -142,7 +142,7 @@ class GqlApi:
142
142
  query: str,
143
143
  variables: dict[str, Any] | None = None,
144
144
  skip_validation: bool = False,
145
- ) -> dict[str, Any] | None:
145
+ ) -> dict[str, Any]:
146
146
  try:
147
147
  result = self.client.execute(
148
148
  gql(query), variables, get_execution_result=True
@@ -172,11 +172,8 @@ class GqlApi:
172
172
  if forbidden_schemas:
173
173
  raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
174
174
 
175
- # This is to appease mypy. This exception won't be thrown as this condition
176
- # is already handled above with AssertionError
177
- if result["data"] is None:
178
- raise GqlApiError("`data` not received in GraphQL payload")
179
-
175
+ # make mypy happy
176
+ assert "data" in result and result["data"] is not None
180
177
  return result["data"]
181
178
 
182
179
  def get_template(self, path: str) -> dict[str, str]:
reconcile/utils/helm.py CHANGED
@@ -11,6 +11,7 @@ from typing import Any
11
11
  import yaml
12
12
 
13
13
  from reconcile.utils import git
14
+ from reconcile.utils.json import json_dumps
14
15
  from reconcile.utils.runtime.sharding import ShardSpec
15
16
 
16
17
 
@@ -70,7 +71,7 @@ def do_template(
70
71
  with tempfile.NamedTemporaryFile(
71
72
  mode="w+", encoding="locale"
72
73
  ) as values_file:
73
- values_file.write(json.dumps(values, cls=JSONEncoder))
74
+ values_file.write(json_dumps(values, cls=JSONEncoder))
74
75
  values_file.flush()
75
76
  cmd = [
76
77
  "helm",
@@ -47,7 +47,7 @@ def flatten(
47
47
  Item = TypeVar("Item")
48
48
 
49
49
 
50
- def find_duplicates(items: Iterable[Item]) -> list[Item]:
50
+ def find_duplicates[Item](items: Iterable[Item]) -> list[Item]:
51
51
  return [item for item, count in Counter(items).items() if count > 1]
52
52
 
53
53
 
@@ -21,7 +21,7 @@ class ImapClient:
21
21
  self._server.login(self.user, self.password)
22
22
  return self
23
23
 
24
- def __exit__(self, *args, **kwargs) -> None:
24
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
25
25
  if self._server:
26
26
  self._server.logout()
27
27
 
@@ -40,7 +40,7 @@ class InstrumentedImage(Image):
40
40
 
41
41
 
42
42
  class InstrumentedSkopeo(Skopeo):
43
- def copy(self, *args: Any, **kwargs: Any) -> bytes | str:
43
+ def copy(self, *args: Any, **kwargs: Any) -> None:
44
44
  metrics.copy_count.labels(
45
45
  integration=INTEGRATION_NAME, shard=SHARDS, shard_id=SHARD_ID
46
46
  ).inc()
@@ -140,7 +140,7 @@ class InternalGroupsClient:
140
140
  with self._api as api:
141
141
  return Group(
142
142
  **api.create_group(
143
- data=group.dict(by_alias=True),
143
+ data=group.model_dump(by_alias=True),
144
144
  )
145
145
  )
146
146
 
@@ -155,6 +155,6 @@ class InternalGroupsClient:
155
155
  return Group(
156
156
  **api.update_group(
157
157
  name=group.name,
158
- data=group.dict(by_alias=True),
158
+ data=group.model_dump(by_alias=True),
159
159
  )
160
160
  )
@@ -27,7 +27,7 @@ class Entity(BaseModel):
27
27
  return hash(self.id)
28
28
 
29
29
 
30
- class Group(BaseModel):
30
+ class Group(BaseModel, validate_by_name=True, validate_by_alias=True):
31
31
  name: str
32
32
  description: str
33
33
  member_approval_type: str = Field("self-service", alias="memberApprovalType")
@@ -35,16 +35,18 @@ class Group(BaseModel):
35
35
  owners: list[Entity]
36
36
  display_name: str = Field(..., alias="displayName")
37
37
  notes: str | None = None
38
- rover_group_member_query: str | None = Field(None, alias="roverGroupMemberQuery")
38
+ rover_group_member_query: str | None = Field(
39
+ None, alias="roverGroupMemberQuery", exclude=True
40
+ )
39
41
  rover_group_inclusions: list[Entity] | None = Field(
40
- None, alias="roverGroupInclusions"
42
+ None, alias="roverGroupInclusions", exclude=True
41
43
  )
42
44
  rover_group_exclusions: list[Entity] | None = Field(
43
- None, alias="roverGroupExclusions"
45
+ None, alias="roverGroupExclusions", exclude=True
44
46
  )
45
47
  members: list[Entity] = []
46
- member_of: list[str] | None = Field(None, alias="memberOf")
47
- namespace: str | None = None
48
+ member_of: list[str] | None = Field(None, alias="memberOf", exclude=True)
49
+ namespace: str | None = Field(None, exclude=True)
48
50
 
49
51
  def __eq__(self, other: object) -> bool:
50
52
  if not isinstance(other, Group):
@@ -58,14 +60,3 @@ class Group(BaseModel):
58
60
  and self.notes == other.notes
59
61
  and set(self.members) == set(other.members)
60
62
  )
61
-
62
- class Config:
63
- allow_population_by_field_name = True
64
- # exclude read-only fields in the json/dict dumps
65
- fields = {
66
- "rover_group_member_query": {"exclude": True},
67
- "rover_group_inclusions": {"exclude": True},
68
- "rover_group_exclusions": {"exclude": True},
69
- "member_of": {"exclude": True},
70
- "namespace": {"exclude": True},
71
- }
@@ -231,10 +231,33 @@ class JenkinsApi:
231
231
 
232
232
  return kwargs
233
233
 
234
+ def _is_parameterized_job(self, job_name: str) -> bool:
235
+ api_query = "tree=property[parameterDefinitions[*]]"
236
+ url = f"{self.url}/job/{job_name}/api/json?{api_query}"
237
+
238
+ res = requests.get(
239
+ url,
240
+ verify=self.ssl_verify,
241
+ auth=(self.user, self.password),
242
+ timeout=60,
243
+ )
244
+
245
+ res.raise_for_status()
246
+
247
+ job_info = res.json()
248
+
249
+ return any(
250
+ prop.get("parameterDefinitions") for prop in job_info.get("property", [])
251
+ )
252
+
234
253
  def trigger_job(self, job_name: str) -> None:
235
254
  kwargs = self.get_crumb_kwargs()
236
255
 
237
- url = f"{self.url}/job/{job_name}/build"
256
+ if self._is_parameterized_job(job_name):
257
+ url = f"{self.url}/job/{job_name}/buildWithParameters"
258
+ else:
259
+ url = f"{self.url}/job/{job_name}/build"
260
+
238
261
  res = requests.post(
239
262
  url,
240
263
  verify=self.ssl_verify,
@@ -15,6 +15,7 @@ from reconcile.checkpoint import url_makes_sense
15
15
  from reconcile.github_org import get_default_config
16
16
  from reconcile.utils import gql
17
17
  from reconcile.utils.aws_api import AWSApi
18
+ from reconcile.utils.datetime_util import utc_now
18
19
  from reconcile.utils.github_api import GithubRepositoryApi
19
20
  from reconcile.utils.helpers import flatten
20
21
  from reconcile.utils.jinja2.extensions import B64EncodeExtension, RaiseErrorExtension
@@ -35,6 +36,7 @@ from reconcile.utils.secret_reader import (
35
36
  SecretReader,
36
37
  SecretReaderBase,
37
38
  )
39
+ from reconcile.utils.sloth import generate_sloth_rules
38
40
  from reconcile.utils.vault import SecretFieldNotFoundError
39
41
 
40
42
 
@@ -43,14 +45,11 @@ class Jinja2TemplateError(Exception):
43
45
  super().__init__("error processing jinja2 template: " + str(msg))
44
46
 
45
47
 
46
- class TemplateRenderOptions(BaseModel):
48
+ class TemplateRenderOptions(BaseModel, frozen=True):
47
49
  trim_blocks: bool
48
50
  lstrip_blocks: bool
49
51
  keep_trailing_newline: bool
50
52
 
51
- class Config:
52
- frozen = True
53
-
54
53
  @classmethod
55
54
  def create(
56
55
  cls,
@@ -73,7 +72,7 @@ def compile_jinja2_template(
73
72
  ) -> Any:
74
73
  if not template_render_options:
75
74
  template_render_options = TemplateRenderOptions.create()
76
- env: dict[str, Any] = template_render_options.dict()
75
+ env: dict[str, Any] = template_render_options.model_dump()
77
76
  if extra_curly:
78
77
  env.update({
79
78
  "block_start_string": "{{%",
@@ -258,9 +257,8 @@ def process_jinja2_template(
258
257
  "s3": lookup_s3_object,
259
258
  "s3_ls": list_s3_objects,
260
259
  "flatten_dict": flatten,
261
- "yesterday": lambda: (datetime.datetime.now() - datetime.timedelta(1)).strftime(
262
- "%Y-%m-%d"
263
- ),
260
+ "yesterday": lambda: (utc_now() - datetime.timedelta(1)).strftime("%Y-%m-%d"),
261
+ "sloth_alerts": generate_sloth_rules,
264
262
  })
265
263
  if "_template_mocks" in vars:
266
264
  for k, v in vars["_template_mocks"].items():
@@ -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)