qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__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 (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
reconcile/acs_rbac.py CHANGED
@@ -65,7 +65,7 @@ class AcsRole(BaseModel):
65
65
  assignments: list[AssignmentPair]
66
66
  permission_set_name: str
67
67
  access_scope: AcsAccessScope
68
- system_default: bool | None
68
+ system_default: bool | None = None
69
69
 
70
70
  @classmethod
71
71
  def build(cls, permission: Permission, usernames: list[str]) -> Self:
@@ -151,7 +151,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
151
151
  for permission in role.oidc_permissions or []:
152
152
  if isinstance(permission, OidcPermissionAcsV1):
153
153
  permission_usernames[
154
- Permission(**permission.dict(by_alias=True))
154
+ Permission(**permission.model_dump(by_alias=True))
155
155
  ].append(user.org_username)
156
156
  return list(starmap(AcsRole.build, permission_usernames.items()))
157
157
 
@@ -1,13 +1,13 @@
1
1
  import logging
2
2
  from collections import defaultdict
3
3
  from datetime import timedelta
4
- from typing import Optional
4
+ from typing import Annotated, Optional
5
5
 
6
6
  from pydantic import (
7
7
  BaseModel,
8
8
  Field,
9
9
  ValidationError,
10
- validator,
10
+ field_validator,
11
11
  )
12
12
  from pydantic.dataclasses import dataclass
13
13
 
@@ -289,13 +289,16 @@ class OrganizationLabelSet(BaseModel):
289
289
 
290
290
  blocked_versions: CSV | None = Field(alias=aus_label_key("blocked-versions"))
291
291
 
292
- sector_max_parallel_upgrades: dict[str, str] = labelset_groupfield(
293
- group_prefix=aus_label_key("sector-max-parallel-upgrades.")
294
- )
292
+ sector_max_parallel_upgrades: Annotated[
293
+ dict[str, str],
294
+ labelset_groupfield(
295
+ group_prefix=aus_label_key("sector-max-parallel-upgrades.")
296
+ ),
297
+ ]
295
298
 
296
- sector_deps: dict[str, CSV] = labelset_groupfield(
297
- group_prefix=aus_label_key("sector-deps.")
298
- )
299
+ sector_deps: Annotated[
300
+ dict[str, CSV], labelset_groupfield(group_prefix=aus_label_key("sector-deps."))
301
+ ]
299
302
  """
300
303
  Each sector with dependencies is represented as a `sector-deps.<sector-name>` label
301
304
  with a CSV formatted list of dependant sectors. The custom `labelset_groupfield``
@@ -357,7 +360,7 @@ def _build_org_upgrade_spec(
357
360
  ]
358
361
 
359
362
  org_labelset = build_labelset(org_labels, OrganizationLabelSet)
360
- final_org = org.copy(deep=True)
363
+ final_org = org.model_copy(deep=True)
361
364
  final_org.blocked_versions = org_labelset.blocked_versions # type: ignore
362
365
  final_org.sectors = org_labelset.sector_dependencies()
363
366
  final_org.inherit_version_data = inherit_version_data
@@ -411,7 +414,7 @@ class ClusterUpgradePolicyLabelSet(BaseModel):
411
414
  """
412
415
 
413
416
  soak_days: int = Field(alias=aus_label_key("soak-days"), ge=0)
414
- workloads: CSV = Field(alias=aus_label_key("workloads"), csv_min_items=1)
417
+ workloads: CSV = Field(alias=aus_label_key("workloads"), min_length=1)
415
418
  schedule: str = Field(alias=aus_label_key("schedule"))
416
419
  mutexes: CSV | None = Field(alias=aus_label_key("mutexes"))
417
420
  sector: str | None = Field(alias=aus_label_key("sector"))
@@ -419,14 +422,14 @@ class ClusterUpgradePolicyLabelSet(BaseModel):
419
422
  version_gate_approvals: CSV | None = Field(
420
423
  alias=aus_label_key("version-gate-approvals")
421
424
  )
422
- _schedule_validator = validator("schedule", allow_reuse=True)(cron_validator)
425
+ _schedule_validator = field_validator("schedule")(cron_validator)
423
426
 
424
427
  def build_labels_dict(self) -> dict[str, str]:
425
428
  """
426
429
  Build a dictionary of all labels in this labelset.
427
430
  """
428
431
  labels = {}
429
- for k, v in self.dict(by_alias=True).items():
432
+ for k, v in self.model_dump(by_alias=True).items():
430
433
  if v is None:
431
434
  continue
432
435
  if isinstance(v, list):
reconcile/aus/base.py CHANGED
@@ -1,4 +1,3 @@
1
- import datetime as dt
2
1
  import logging
3
2
  import sys
4
3
  from abc import (
@@ -17,7 +16,7 @@ from typing import (
17
16
  )
18
17
 
19
18
  from croniter import croniter
20
- from pydantic import BaseModel, Extra
19
+ from pydantic import BaseModel
21
20
  from requests.exceptions import HTTPError
22
21
  from semver import VersionInfo
23
22
 
@@ -71,6 +70,12 @@ from reconcile.utils.clusterhealth.telemeter import (
71
70
  TELEMETER_SOURCE,
72
71
  TelemeterClusterHealthProvider,
73
72
  )
73
+ from reconcile.utils.datetime_util import (
74
+ ensure_utc,
75
+ from_utc_iso_format,
76
+ to_utc_seconds_iso_format,
77
+ utc_now,
78
+ )
74
79
  from reconcile.utils.defer import defer
75
80
  from reconcile.utils.disabled_integrations import integration_is_enabled
76
81
  from reconcile.utils.filtering import remove_none_values_from_dict
@@ -399,12 +404,12 @@ class AbstractUpgradePolicy(ABC, BaseModel):
399
404
 
400
405
  cluster: OCMCluster
401
406
 
402
- id: str | None
403
- next_run: str | None
404
- schedule: str | None
407
+ id: str | None = None
408
+ next_run: str | None = None
409
+ schedule: str | None = None
405
410
  schedule_type: str
406
411
  version: str
407
- state: str | None
412
+ state: str | None = None
408
413
 
409
414
  @abstractmethod
410
415
  def create(self, ocm_api: OCMBaseClient) -> None:
@@ -420,20 +425,17 @@ class AbstractUpgradePolicy(ABC, BaseModel):
420
425
 
421
426
 
422
427
  def addon_upgrade_policy_soonest_next_run() -> str:
423
- now = datetime.now(tz=dt.UTC)
428
+ now = utc_now()
424
429
  next_run = now + timedelta(minutes=MIN_DELTA_MINUTES)
425
- return next_run.strftime("%Y-%m-%dT%H:%M:%SZ")
430
+ return to_utc_seconds_iso_format(next_run)
426
431
 
427
432
 
428
- class AddonUpgradePolicy(AbstractUpgradePolicy):
433
+ class AddonUpgradePolicy(AbstractUpgradePolicy, arbitrary_types_allowed=True):
429
434
  """Class to create and delete Addon upgrade policies in OCM"""
430
435
 
431
436
  addon_id: str
432
437
  addon_service: AddonService
433
438
 
434
- class Config:
435
- arbitrary_types_allowed = True
436
-
437
439
  def create(self, ocm_api: OCMBaseClient) -> None:
438
440
  self.addon_service.create_addon_upgrade_policy(
439
441
  ocm_api=ocm_api,
@@ -516,9 +518,10 @@ class ControlPlaneUpgradePolicy(AbstractUpgradePolicy):
516
518
 
517
519
 
518
520
  class NodePoolUpgradePolicy(AbstractUpgradePolicy):
519
- node_pool: str
520
521
  """Class to create NodePoolUpgradePolicies in OCM"""
521
522
 
523
+ node_pool: str
524
+
522
525
  def create(self, ocm_api: OCMBaseClient) -> None:
523
526
  policy = {
524
527
  "version": self.version,
@@ -545,7 +548,7 @@ class NodePoolUpgradePolicy(AbstractUpgradePolicy):
545
548
  return f"node pool upgrade policy - {remove_none_values_from_dict(details)}"
546
549
 
547
550
 
548
- class UpgradePolicyHandler(BaseModel, extra=Extra.forbid):
551
+ class UpgradePolicyHandler(BaseModel, extra="forbid"):
549
552
  """Class to handle upgrade policy actions"""
550
553
 
551
554
  action: str
@@ -638,8 +641,8 @@ def update_history(
638
641
  version_data (VersionData): version data, including history of soakdays
639
642
  upgrade_policies (list): query results of clusters upgrade policies
640
643
  """
641
- now = datetime.utcnow()
642
- check_in = version_data.check_in or now
644
+ now = utc_now()
645
+ check_in = ensure_utc(version_data.check_in or now)
643
646
 
644
647
  # we iterate over clusters upgrade policies and update the version history
645
648
  for spec in org_upgrade_spec.specs:
@@ -930,7 +933,7 @@ def verify_schedule_should_skip(
930
933
  # immediately
931
934
  delay_minutes = 1 if addon_id else MIN_DELTA_MINUTES
932
935
  next_schedule = iter.get_next(
933
- dt.datetime, start_time=now + timedelta(minutes=delay_minutes)
936
+ datetime, start_time=now + timedelta(minutes=delay_minutes)
934
937
  )
935
938
  next_schedule_in_seconds = (next_schedule - now).total_seconds()
936
939
  next_schedule_in_hours = next_schedule_in_seconds / 3600 # seconds in hour
@@ -947,7 +950,7 @@ def verify_schedule_should_skip(
947
950
  f"[{desired.org.org_id}/{desired.org.name}/{desired.cluster.name}] skipping cluster with no upcoming upgrade"
948
951
  )
949
952
  return None
950
- return next_schedule.strftime("%Y-%m-%dT%H:%M:%SZ")
953
+ return to_utc_seconds_iso_format(next_schedule)
951
954
 
952
955
 
953
956
  def verify_max_upgrades_should_skip(
@@ -1024,8 +1027,8 @@ def _calculate_node_pool_diffs(
1024
1027
  ) -> UpgradePolicyHandler | None:
1025
1028
  for pool in spec.node_pools:
1026
1029
  if parse_semver(pool.version).match(f"<{spec.current_version}"):
1027
- next_schedule = (now + timedelta(minutes=MIN_DELTA_MINUTES)).strftime(
1028
- "%Y-%m-%dT%H:%M:%SZ"
1030
+ next_schedule = to_utc_seconds_iso_format(
1031
+ now + timedelta(minutes=MIN_DELTA_MINUTES)
1029
1032
  )
1030
1033
  return UpgradePolicyHandler(
1031
1034
  action="create",
@@ -1082,7 +1085,7 @@ def calculate_diff(
1082
1085
  set_upgrading(spec.cluster.id, spec.effective_mutexes, sector_name)
1083
1086
 
1084
1087
  addon_service = init_addon_service(desired_state.org.environment)
1085
- now = datetime.utcnow()
1088
+ now = utc_now()
1086
1089
  gates = get_version_gates(ocm_api)
1087
1090
  for spec in desired_state.specs:
1088
1091
  sector_name = spec.upgrade_policy.conditions.sector
@@ -1119,12 +1122,10 @@ def calculate_diff(
1119
1122
  UpgradePolicyHandler(
1120
1123
  action="create",
1121
1124
  policy=AddonUpgradePolicy(
1122
- action="create",
1123
1125
  cluster=spec.cluster,
1124
1126
  version=version,
1125
1127
  schedule_type="manual",
1126
1128
  addon_id=addon_id,
1127
- upgrade_type="ADDON",
1128
1129
  addon_service=addon_service,
1129
1130
  ),
1130
1131
  )
@@ -1297,10 +1298,8 @@ def remaining_soak_day_metric_values_for_cluster(
1297
1298
  remaining_soakdays[idx] = UPGRADE_STARTED_METRIC_VALUE
1298
1299
  if current_upgrade.next_run:
1299
1300
  # if an upgrade runs for over 6 hours, we mark it as a long running upgrade
1300
- next_run = datetime.strptime(
1301
- current_upgrade.next_run, "%Y-%m-%dT%H:%M:%SZ"
1302
- )
1303
- now = datetime.utcnow()
1301
+ next_run = from_utc_iso_format(current_upgrade.next_run)
1302
+ now = utc_now()
1304
1303
  hours_ago = (now - next_run).total_seconds() / 3600
1305
1304
  if hours_ago >= 6:
1306
1305
  remaining_soakdays[idx] = UPGRADE_LONG_RUNNING_METRIC_VALUE
@@ -1,4 +1,3 @@
1
- import json
2
1
  from collections.abc import Iterable
3
2
  from datetime import datetime
4
3
  from typing import (
@@ -20,6 +19,17 @@ class WorkloadHistory(BaseModel):
20
19
  soak_days: float = 0.0
21
20
  reporting: list[str] = Field(default_factory=list)
22
21
 
22
+ def __eq__(self, value: Any) -> bool:
23
+ if isinstance(value, WorkloadHistory):
24
+ return super().__eq__(value)
25
+
26
+ if isinstance(value, dict):
27
+ return self.soak_days == value.get("soak_days", 0.0) and sorted(
28
+ self.reporting
29
+ ) == sorted(value.get("reporting", []))
30
+
31
+ return False
32
+
23
33
 
24
34
  class VersionHistory(BaseModel):
25
35
  workloads: dict[str, WorkloadHistory] = Field(default_factory=dict)
@@ -38,7 +48,7 @@ class Stats(BaseModel):
38
48
 
39
49
  min_version: str
40
50
  min_version_per_workload: dict[str, str] = Field(default_factory=dict)
41
- inherited: Optional["Stats"]
51
+ inherited: Optional["Stats"] = None
42
52
 
43
53
  def inherit(self, added: "Stats") -> None:
44
54
  """adds the provided stats to our inherited data
@@ -93,12 +103,12 @@ class VersionData(BaseModel):
93
103
  upgrade policies.
94
104
  """
95
105
 
96
- check_in: datetime | None
106
+ check_in: datetime | None = None
97
107
  versions: dict[str, VersionHistory] = Field(default_factory=dict)
98
- stats: Stats | None
108
+ stats: Stats | None = None
99
109
 
100
110
  def jsondict(self) -> dict[str, Any]:
101
- return json.loads(self.json(exclude_none=True))
111
+ return self.model_dump(mode="json", exclude_none=True)
102
112
 
103
113
  def save(self, state: State, ocm_name: str) -> None:
104
114
  state.add(ocm_name, self.jsondict(), force=True)
reconcile/aus/models.py CHANGED
@@ -232,7 +232,7 @@ class SectorConfigError(Exception):
232
232
 
233
233
  class Sector(BaseModel):
234
234
  name: str
235
- max_parallel_upgrades: str | None
235
+ max_parallel_upgrades: str | None = None
236
236
  dependencies: list[Sector] = Field(default_factory=list)
237
237
  _specs: dict[str, ClusterUpgradeSpec] = PrivateAttr(default_factory=dict)
238
238
 
@@ -20,6 +20,7 @@ from reconcile.gql_definitions.automated_actions.instance import (
20
20
  AutomatedActionExternalResourceFlushElastiCacheV1,
21
21
  AutomatedActionExternalResourceRdsRebootV1,
22
22
  AutomatedActionExternalResourceRdsSnapshotV1,
23
+ AutomatedActionOpenshiftTriggerCronjobV1,
23
24
  AutomatedActionOpenshiftWorkloadDeleteV1,
24
25
  AutomatedActionOpenshiftWorkloadRestartArgumentV1,
25
26
  AutomatedActionOpenshiftWorkloadRestartV1,
@@ -82,7 +83,7 @@ class AutomatedActionsConfigIntegration(
82
83
  query_func = gql.get_api().query
83
84
  return {
84
85
  "automated_actions_instances": [
85
- c.dict() for c in self.get_automated_actions_instances(query_func)
86
+ c.model_dump() for c in self.get_automated_actions_instances(query_func)
86
87
  ]
87
88
  }
88
89
 
@@ -167,7 +168,7 @@ class AutomatedActionsConfigIntegration(
167
168
  case AutomatedActionActionListV1():
168
169
  # no special handling needed, just dump the values
169
170
  parameters.extend(
170
- arg.dict(exclude_none=True, exclude_defaults=True)
171
+ arg.model_dump(exclude_none=True, exclude_defaults=True)
171
172
  for arg in action.action_list_arguments or []
172
173
  )
173
174
  case AutomatedActionExternalResourceFlushElastiCacheV1():
@@ -205,6 +206,17 @@ class AutomatedActionsConfigIntegration(
205
206
  "account": f"^{rds_snapshot_er.provisioner.name}$",
206
207
  "identifier": rds_snapshot_arg.identifier,
207
208
  })
209
+ case AutomatedActionOpenshiftTriggerCronjobV1():
210
+ parameters.extend(
211
+ {
212
+ # all parameter values are regexes in the OPA policy
213
+ # therefore, cluster and namespace must be fixed to the current strings
214
+ "cluster": f"^{arg.namespace.cluster.name}$",
215
+ "namespace": f"^{arg.namespace.name}$",
216
+ "cronjob": arg.cronjob,
217
+ }
218
+ for arg in action.openshift_trigger_cronjob_arguments
219
+ )
208
220
  case AutomatedActionOpenshiftWorkloadDeleteV1():
209
221
  parameters.extend(
210
222
  {
@@ -257,7 +269,7 @@ class AutomatedActionsConfigIntegration(
257
269
  {
258
270
  "users": {user.username: sorted(user.roles) for user in users},
259
271
  "roles": {
260
- role: [policy.dict() for policy in policies]
272
+ role: [policy.model_dump() for policy in policies]
261
273
  for role, policies in roles.items()
262
274
  },
263
275
  },
@@ -1,5 +1,4 @@
1
1
  from collections.abc import Callable, Iterable
2
- from datetime import UTC, datetime
3
2
  from typing import Any
4
3
 
5
4
  import jinja2
@@ -26,6 +25,7 @@ from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
26
25
  from reconcile.utils import gql, metrics
27
26
  from reconcile.utils.aws_api_typed.api import AWSApi, AWSStaticCredentials
28
27
  from reconcile.utils.aws_api_typed.iam import AWSAccessKey
28
+ from reconcile.utils.datetime_util import utc_now
29
29
  from reconcile.utils.defer import defer
30
30
  from reconcile.utils.disabled_integrations import integration_is_enabled
31
31
  from reconcile.utils.runtime.integration import (
@@ -42,14 +42,14 @@ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
42
42
 
43
43
 
44
44
  class AwsAccountMgmtIntegrationParams(PydanticRunParams):
45
- account_name: str | None
45
+ account_name: str | None = None
46
46
  flavor: str
47
47
  organization_account_role: str = "OrganizationAccountAccessRole"
48
48
  default_tags: dict[str, str] = {}
49
49
  initial_user_name: str = "terraform"
50
50
  initial_user_policy_arn: str = "arn:aws:iam::aws:policy/AdministratorAccess"
51
51
  initial_user_secret_vault_path: str = (
52
- "app-sre-v2/creds/terraform/{account_name}/config"
52
+ "app-sre-v2/creds/terraform/{account_name}/config" # noqa: RUF027
53
53
  )
54
54
  # To avoid the accidental deletion of the resource file, explicitly set the
55
55
  # qontract.cli option in the integration extraArgs!
@@ -76,9 +76,9 @@ class AwsAccountMgmtIntegration(
76
76
  query_func, account_name=self.params.account_name
77
77
  )
78
78
  return {
79
- "payer_accounts": [account.dict() for account in payer_accounts],
79
+ "payer_accounts": [account.model_dump() for account in payer_accounts],
80
80
  "non_organization_accounts": [
81
- account.dict() for account in non_organization_accounts
81
+ account.model_dump() for account in non_organization_accounts
82
82
  ],
83
83
  }
84
84
 
@@ -98,10 +98,10 @@ class AwsAccountMgmtIntegration(
98
98
  lstrip_blocks=True,
99
99
  keep_trailing_newline=True,
100
100
  ).render({
101
- "accountRequest": account_request.dict(by_alias=True),
101
+ "accountRequest": account_request.model_dump(by_alias=True),
102
102
  "uid": uid,
103
103
  "settings": settings,
104
- "timestamp": int(datetime.now(tz=UTC).timestamp()),
104
+ "timestamp": int(utc_now().timestamp()),
105
105
  })
106
106
  return tmpl
107
107
 
@@ -187,7 +187,7 @@ class AwsAccountMgmtIntegration(
187
187
  template=account_template,
188
188
  account_request=account_request,
189
189
  uid=uid,
190
- settings=self.params.dict(),
190
+ settings=self.params.model_dump(),
191
191
  ),
192
192
  account_request_file_path=f"data/{account_request.path.strip('/')}",
193
193
  )
@@ -35,7 +35,7 @@ class Quota(Protocol):
35
35
  quota_code: str
36
36
  value: float
37
37
 
38
- def dict(self) -> dict[str, Any]: ...
38
+ def model_dump(self) -> dict[str, Any]: ...
39
39
 
40
40
 
41
41
  class Contact(Protocol):
@@ -44,7 +44,7 @@ class Contact(Protocol):
44
44
  email: str
45
45
  phone_number: str
46
46
 
47
- def dict(self) -> dict[str, Any]: ...
47
+ def model_dump(self) -> dict[str, Any]: ...
48
48
 
49
49
 
50
50
  class AWSReconciler:
@@ -167,7 +167,7 @@ class AWSReconciler:
167
167
  self, aws_api: AWSApi, name: str, quotas: Iterable[Quota]
168
168
  ) -> list[str] | None:
169
169
  """Request service quota changes."""
170
- quotas_dict = [q.dict() for q in quotas]
170
+ quotas_dict = [q.model_dump() for q in quotas]
171
171
  with self.state.transaction(
172
172
  state_key(name, TASK_REQUEST_SERVICE_QUOTA)
173
173
  ) as _state:
@@ -26,6 +26,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
26
26
  )
27
27
  from reconcile.utils import gql
28
28
  from reconcile.utils.aws_api import AWSApi
29
+ from reconcile.utils.datetime_util import from_utc_iso_format, utc_now
29
30
  from reconcile.utils.defer import defer
30
31
  from reconcile.utils.parse_dhms_duration import dhms_to_seconds
31
32
  from reconcile.utils.secret_reader import create_secret_reader
@@ -39,15 +40,12 @@ QONTRACT_INTEGRATION = "aws_ami_cleanup"
39
40
  MANAGED_TAG = {"Key": "managed_by_integration", "Value": QONTRACT_INTEGRATION}
40
41
 
41
42
 
42
- class AWSAmi(BaseModel):
43
+ class AWSAmi(BaseModel, frozen=True):
43
44
  name: str
44
45
  image_id: str
45
46
  creation_date: datetime
46
47
  snapshot_ids: list[str]
47
48
 
48
- class Config:
49
- frozen = True
50
-
51
49
 
52
50
  def get_aws_amis_from_launch_templates(ec2_client: EC2Client) -> set[str]:
53
51
  amis = set()
@@ -77,7 +75,7 @@ def get_aws_amis(
77
75
  owner: str,
78
76
  regex: str,
79
77
  age_in_seconds: int,
80
- utc_now: datetime,
78
+ now: datetime,
81
79
  ) -> list[AWSAmi]:
82
80
  """Get amis that match regex older than given age"""
83
81
 
@@ -89,10 +87,8 @@ def get_aws_amis(
89
87
  if not re.search(pattern, image["Name"]):
90
88
  continue
91
89
 
92
- creation_date = datetime.strptime(
93
- image["CreationDate"], "%Y-%m-%dT%H:%M:%S.%fZ"
94
- )
95
- current_delta = utc_now - creation_date
90
+ creation_date = from_utc_iso_format(image["CreationDate"])
91
+ current_delta = now - creation_date
96
92
  delete_delta = timedelta(seconds=age_in_seconds)
97
93
 
98
94
  if current_delta < delete_delta:
@@ -135,7 +131,7 @@ def get_region(
135
131
 
136
132
  @defer
137
133
  def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) -> None:
138
- utc_now = datetime.utcnow()
134
+ now = utc_now()
139
135
  gqlapi = gql.get_api()
140
136
  aws_accounts = aws_accounts_query(gqlapi.query).accounts
141
137
 
@@ -177,7 +173,7 @@ def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) ->
177
173
  # Build AWSApi object. We will use all those accounts listed in ami_accounts since
178
174
  # we will also need to look for used AMIs.
179
175
  accounts_dicted = [
180
- account.dict(by_alias=True)
176
+ account.model_dump(by_alias=True)
181
177
  for account in aws_accounts or []
182
178
  if account.name in ami_accounts
183
179
  ]
@@ -222,7 +218,7 @@ def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) ->
222
218
  owner=account.uid,
223
219
  regex=cleanup_config.regex,
224
220
  age_in_seconds=age_in_seconds,
225
- utc_now=utc_now,
221
+ now=now,
226
222
  )
227
223
 
228
224
  for ami in aws_amis:
@@ -1,17 +1,19 @@
1
1
  import logging
2
+ import re
2
3
  from collections.abc import (
3
- Callable,
4
4
  Iterable,
5
5
  Mapping,
6
6
  )
7
7
  from typing import Any
8
8
 
9
9
  from reconcile import queries
10
+ from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
11
+ from reconcile.typed_queries.external_resources import get_settings
10
12
  from reconcile.utils.aws_api import AWSApi
11
- from reconcile.utils.defer import defer
12
13
 
13
14
  QONTRACT_INTEGRATION = "aws-ami-share"
14
- MANAGED_TAG = {"Key": "managed_by_integration", "Value": QONTRACT_INTEGRATION}
15
+
16
+ MANAGED_TAG = {"managed_by_integration": QONTRACT_INTEGRATION}
15
17
 
16
18
 
17
19
  def filter_accounts(accounts: Iterable[dict[str, Any]]) -> list[dict[str, Any]]:
@@ -37,65 +39,70 @@ def get_region(
37
39
  return region
38
40
 
39
41
 
40
- @defer
41
- def run(dry_run: bool, defer: Callable | None = None) -> None:
42
+ def share_ami(
43
+ dry_run: bool,
44
+ src_account: Mapping[str, Any],
45
+ share: Mapping[str, Any],
46
+ default_tags: dict[str, str],
47
+ aws_api: AWSApi,
48
+ ) -> None:
49
+ dst_account = share["account"]
50
+ regex = re.compile(share["regex"])
51
+ region = get_region(share, src_account, dst_account)
52
+ src_amis = aws_api.get_amis_details(src_account, src_account, regex, region)
53
+ dst_amis = aws_api.get_amis_details(dst_account, src_account, regex, region)
54
+
55
+ for ami_id, src_ami_tags in src_amis.items():
56
+ dst_ami_tags = dst_amis.get(ami_id)
57
+ if dst_ami_tags is None:
58
+ logging.info([
59
+ "share_ami",
60
+ src_account["name"],
61
+ dst_account["name"],
62
+ ami_id,
63
+ ])
64
+ if not dry_run:
65
+ aws_api.share_ami(src_account, dst_account["uid"], ami_id, region)
66
+ dst_account_tags = default_tags | get_aws_account_tags(
67
+ dst_account.get("organization", None)
68
+ )
69
+ desired_tags = src_ami_tags | dst_account_tags | MANAGED_TAG
70
+ current_tags = dst_ami_tags or {}
71
+
72
+ if desired_tags != current_tags:
73
+ logging.info([
74
+ "tag_shared_ami",
75
+ dst_account["name"],
76
+ ami_id,
77
+ desired_tags,
78
+ ])
79
+ if not dry_run:
80
+ aws_api.create_tags(dst_account, ami_id, desired_tags)
81
+
82
+
83
+ def run(dry_run: bool) -> None:
42
84
  accounts = queries.get_aws_accounts(sharing=True)
43
85
  sharing_accounts = filter_accounts(accounts)
44
86
  settings = queries.get_app_interface_settings()
45
- aws_api = AWSApi(1, sharing_accounts, settings=settings, init_users=False)
46
- if defer:
47
- defer(aws_api.cleanup)
48
-
49
- for src_account in sharing_accounts:
50
- sharing = src_account.get("sharing")
51
- if not sharing:
52
- continue
53
- for share in sharing:
54
- if share["provider"] != "ami":
55
- continue
56
- dst_account = share["account"]
57
- regex = share["regex"]
58
- region = get_region(share, src_account, dst_account)
59
- src_amis = aws_api.get_amis_details(src_account, src_account, regex, region)
60
- dst_amis = aws_api.get_amis_details(dst_account, src_account, regex, region)
61
-
62
- for src_ami in src_amis:
63
- src_ami_id = src_ami["image_id"]
64
- found_dst_amis = [d for d in dst_amis if d["image_id"] == src_ami_id]
65
- if not found_dst_amis:
66
- logging.info([
67
- "share_ami",
68
- src_account["name"],
69
- dst_account["name"],
70
- src_ami_id,
71
- ])
72
- if not dry_run:
73
- aws_api.share_ami(
74
- src_account, dst_account["uid"], src_ami_id, region
75
- )
76
- # we assume an unshared ami does not have tags
77
- found_dst_amis = [{"image_id": src_ami_id, "tags": []}]
78
-
79
- dst_ami = found_dst_amis[0]
80
- dst_ami_id = dst_ami["image_id"]
81
- dst_ami_tags = dst_ami["tags"]
82
- if MANAGED_TAG not in dst_ami_tags:
83
- logging.info([
84
- "tag_shared_ami",
85
- dst_account["name"],
86
- dst_ami_id,
87
- MANAGED_TAG,
88
- ])
89
- if not dry_run:
90
- aws_api.create_tag(dst_account, dst_ami_id, MANAGED_TAG)
91
- src_ami_tags = src_ami["tags"]
92
- for src_tag in src_ami_tags:
93
- if src_tag not in dst_ami_tags:
94
- logging.info([
95
- "tag_shared_ami",
96
- dst_account["name"],
97
- dst_ami_id,
98
- src_tag,
99
- ])
100
- if not dry_run:
101
- aws_api.create_tag(dst_account, dst_ami_id, src_tag)
87
+ try:
88
+ default_tags = get_settings().default_tags
89
+ except ValueError:
90
+ # no external resources settings found
91
+ default_tags = {}
92
+
93
+ with AWSApi(
94
+ 1,
95
+ sharing_accounts,
96
+ settings=settings,
97
+ init_users=False,
98
+ ) as aws_api:
99
+ for src_account in sharing_accounts:
100
+ for share in src_account.get("sharing") or []:
101
+ if share["provider"] == "ami":
102
+ share_ami(
103
+ dry_run=dry_run,
104
+ src_account=src_account,
105
+ share=share,
106
+ default_tags=default_tags,
107
+ aws_api=aws_api,
108
+ )