qontract-reconcile 0.10.1rc536__py3-none-any.whl → 0.10.1rc538__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc536
3
+ Version: 0.10.1rc538
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -1,5 +1,5 @@
1
1
  reconcile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- reconcile/acs_policies.py,sha256=Mop44Z5T0DxXghme13ZVvy1hr5KphZKzc_ZevxAJBQ0,8784
2
+ reconcile/acs_policies.py,sha256=e_kVyP4rGm3fJzq10Anr8scZoPenlmTcoS5REK0d2T0,9144
3
3
  reconcile/acs_rbac.py,sha256=YoKu5wTRTtb3EGT0PV3r279LDgvw2ECb-0_0j4suScg,23032
4
4
  reconcile/aws_ami_share.py,sha256=eeu0TI3M5yyUaozyAq_aW3tir-9be4YFguOXvIvKHSo,3757
5
5
  reconcile/aws_ecr_image_pull_secrets.py,sha256=TGEc_0nv8oxV2HqA8VdcM4HHP-B1YqmNOOU6FPwVFTY,2328
@@ -9,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
9
9
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
10
10
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
11
11
  reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
12
- reconcile/cli.py,sha256=ek511mirANFglUXz_lrnIjmHZ48ZDJOx-53OFf-fSO4,84491
12
+ reconcile/cli.py,sha256=LFsNTM36MINQ_iDqIpXlJPrUcLUL7BUEE4GpPG2LmaQ,85546
13
13
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
14
14
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
15
15
  reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
@@ -111,7 +111,7 @@ reconcile/terraform_cloudflare_dns.py,sha256=auU4bzeLwd4S8D8oqpqJbrCUoEdELXrgi7v
111
111
  reconcile/terraform_cloudflare_resources.py,sha256=EbQQaoDnZ7brvRCpbFtwlD7KLk2hDVNcjhrJGaAywEk,15023
112
112
  reconcile/terraform_cloudflare_users.py,sha256=1EbTHwJgiPkJpMP-Ag340QNgGK3mXn3dcC3DpLakudM,13987
113
113
  reconcile/terraform_repo.py,sha256=c0GZFuY3rCm6VHjHqYbsgOHrEkRWKF_1LrMThsn2XDw,16127
114
- reconcile/terraform_resources.py,sha256=x5Do4xBBhjJdIVRi0Gy4h-ryCCZ6kU7bT_iB0_mGing,17105
114
+ reconcile/terraform_resources.py,sha256=3-q0WyCzBbfgvfDjCEKGp09bNGgxtbzUs9jEeoLr6u4,19176
115
115
  reconcile/terraform_tgw_attachments.py,sha256=_g7QSHM03YZzTU7O189S4HYtUn7WmwOBq67G4AieU24,15298
116
116
  reconcile/terraform_users.py,sha256=kXRUxCUchKCP2dbXXOzctynqMii4oyCP6bYZHQTrlTg,10202
117
117
  reconcile/terraform_vpc_peerings.py,sha256=rnDH1u93OyzrBM8Hib0HwSnlxZtx4ScRQaZAcn3mx-k,25402
@@ -375,7 +375,7 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
375
375
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
376
376
  reconcile/test/conftest.py,sha256=rQousYrxUz-EwAIbsYO6bIwR1B4CrOz9y_zaUVo2lfI,4466
377
377
  reconcile/test/fixtures.py,sha256=9SDWAUlSd1rCx7z3GhULHcpr-I6FyCsXxaFAZIqYQsQ,591
378
- reconcile/test/test_acs_policies.py,sha256=2hioxQ1AlbtW-jerw9YiQ8aIb0YjFpzDFs1j6pvn-Nc,15133
378
+ reconcile/test/test_acs_policies.py,sha256=hMnCX9KdLRKb53gYXK4JUR5yJwhczJRyyUPywDeLeLg,15716
379
379
  reconcile/test/test_acs_rbac.py,sha256=lvNd8GY0-GHzcOdOn13QWdrqbBXXKzNT7EEDHNH7cjM,28272
380
380
  reconcile/test/test_aggregated_list.py,sha256=iiWitQuNYC58aimWaiBoE4NROHjr1NCgQ91MnHEG_Ro,6412
381
381
  reconcile/test/test_amtool.py,sha256=vxRhGieeydMBOb9UI2ziMHjJa8puMeGNsUhGhy-yMnk,1032
@@ -440,7 +440,7 @@ reconcile/test/test_terraform_cloudflare_dns.py,sha256=aQTXX8Vr4h9aWvJZTnpZEhMGY
440
440
  reconcile/test/test_terraform_cloudflare_resources.py,sha256=NK_uktyWihkQ3gMN4bCaKerpi43CXAVYGIKTfcz05rY,13550
441
441
  reconcile/test/test_terraform_cloudflare_users.py,sha256=RAFtMMdqZha3jNnNNsqbNQQUDSqUzdoM63rCw7fs4Fo,27456
442
442
  reconcile/test/test_terraform_repo.py,sha256=soKFJfF8tWIimDs39RQl3Hnh-Od-bR4PfnEA2s1UprM,11552
443
- reconcile/test/test_terraform_resources.py,sha256=1ny_QSFuRjV9jxZY8EeT4NVJ5dMv7cLrEEIx_cBpjgk,9075
443
+ reconcile/test/test_terraform_resources.py,sha256=O8kCYxGKqILbbCP86eJ3ESjatdPa1m3wQYXxrVfc_eo,15082
444
444
  reconcile/test/test_terraform_tgw_attachments.py,sha256=cAq6exc-K-jtLla1CZUZQzVnBkyDnIlL7jybnddhLKc,36861
445
445
  reconcile/test/test_terraform_users.py,sha256=Xn4y6EcxnNQb6XcPoOhz_Ikxmh9Nrsu88OM1scN9hzY,5434
446
446
  reconcile/test/test_terraform_vpc_peerings.py,sha256=ubcsKh0TrUIwuI1-W3ETIgzsFvzAyeoFmEJFC-IK6JY,20538
@@ -524,13 +524,14 @@ reconcile/utils/defer.py,sha256=SniUsbgOEs9Pa8JkecLu0F94O63yQPByKXaElDYe0FI,377
524
524
  reconcile/utils/differ.py,sha256=kJmUp9ZffFPSUEviaAw3s9c92ErwRJeHaRexGPai7wA,7643
525
525
  reconcile/utils/disabled_integrations.py,sha256=avdDsFyl_LdTsrPVzlcIhWzT_V4C4MXw1ZC__aOtluE,1126
526
526
  reconcile/utils/dnsutils.py,sha256=VX4gDQXpiMYVuT0pvNbzzSgfqmsWOM2qtjNQHUyIYF8,370
527
- reconcile/utils/early_exit_cache.py,sha256=EOZK-Z3w9SXooa42p-mwCa6LbIxZFJ7qWlyUcXsus7w,2344
527
+ reconcile/utils/early_exit_cache.py,sha256=2uaiYIILA64D8mqqPj-GSvVBa80bN5XQRKz9x33iIpc,2304
528
528
  reconcile/utils/elasticsearch_exceptions.py,sha256=UY5Z3y2hw7T73sPJ6dHmUybegiIophrKFdTfdsOa6UY,379
529
529
  reconcile/utils/environ.py,sha256=VnW3zp6Un_UJn5BU4FU8RfhuqtZp0s-VeuuHnqC_WcQ,515
530
530
  reconcile/utils/exceptions.py,sha256=DwfnWUpVOotpP79RWZ2pycmG6nKCL00RBIeZLYkQPW4,635
531
531
  reconcile/utils/expiration.py,sha256=BXwKE50sNIV-Lszke97fxitNkLxYszoOLW1LBgp_yqg,1246
532
+ reconcile/utils/extended_early_exit.py,sha256=gLWRtgzRB584iX4pVfGjfZxbIDd_AQPYu8MkQkReA3U,5688
532
533
  reconcile/utils/external_resource_spec.py,sha256=OGPKH3IKXgJszRTgE5U_QKgU-s4BHQnx97Lj-Krz46k,6655
533
- reconcile/utils/external_resources.py,sha256=eF9Wup8zbLWx56WoA0FlqguT6BRXRYgoyN3cbmpT_Dk,7443
534
+ reconcile/utils/external_resources.py,sha256=a2CkJ3KLociYBnc_9F2VWfZGWMhzDl6fDNhwo2U-MWU,7501
534
535
  reconcile/utils/filtering.py,sha256=zZnHH0u0SaTDyzuFXZ_mREURGLvjEqQIQy4z-7QBVlc,419
535
536
  reconcile/utils/git.py,sha256=Qad7mfPuS9s7eKODeWSewehwSGgJPCbQuLda1qg_6GA,1522
536
537
  reconcile/utils/git_secrets.py,sha256=0wGNL5mvDtVPRuu3vEQgld1Am64gIDJHtmu1_ZKxMAI,1973
@@ -553,7 +554,7 @@ reconcile/utils/keycloak.py,sha256=UqOsAcHKmmIunroWB5YzC1fUZ3S3aq6L7trn6vLRmXY,3
553
554
  reconcile/utils/ldap_client.py,sha256=ho4veSrHqQWs0YhLFyKeD-duCwY8Nc5gUIA5qLENuMY,2502
554
555
  reconcile/utils/lean_terraform_client.py,sha256=zReyNPJbr2uOdrdh8Qfe-OZQBoRwxb5Za_ddeoUCYVk,4064
555
556
  reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
556
- reconcile/utils/metrics.py,sha256=r93j_9WmsR4jC9uqgGrkzk9gycolhZhbsomHcpmJv8g,18297
557
+ reconcile/utils/metrics.py,sha256=7nXdctmZ0UtGMHPpS3V55sfH4xpMPqdYaJ3JKAUc_sM,18474
557
558
  reconcile/utils/models.py,sha256=R3wF68yiaT0H2sQVMmHhHSTGI3JKKYEVjoizhHkySn8,4533
558
559
  reconcile/utils/oc.py,sha256=iAoM7zSm72zBOmCNW19ttAvm76tUgS26vlpb8SvVYwU,64058
559
560
  reconcile/utils/oc_connection_parameters.py,sha256=85slrnDigYwYmzhyceVkMElWzFArp4ge1d-fHXVqh0w,9729
@@ -580,8 +581,8 @@ reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4d
580
581
  reconcile/utils/state.py,sha256=SAa6QLHu9lr0yqLCBy2AypNx1IPCJWlrRBrvlzAKsOU,14505
581
582
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
582
583
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
583
- reconcile/utils/terraform_client.py,sha256=V7AMQOEU4tvUOT-LQN2cXLqcphD5L93PMGMfurQQyPY,31753
584
- reconcile/utils/terrascript_aws_client.py,sha256=wA6AcAqbS2tIuqh8soMfpdpuf0IbAJWjONMmffJ-0vA,265467
584
+ reconcile/utils/terraform_client.py,sha256=_jBriLBwU005bDxWlq7CRByOkVCfiH47oBzB0ArNAY8,31901
585
+ reconcile/utils/terrascript_aws_client.py,sha256=Ht2akaR4bRERLoyN_Zh2JBbN1-p-ofXICqW-oXzGcFk,265789
585
586
  reconcile/utils/three_way_diff_strategy.py,sha256=nyqeQsLCoPI6e16k2CF3b9KNgQLU-rPf5RtfdUfVMwE,4468
586
587
  reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
587
588
  reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,3601
@@ -590,7 +591,7 @@ reconcile/utils/vaultsecretref.py,sha256=3Ed2uBy36TzSvL0B-l4FoWQqB2SbBKDKEuUPIO6
590
591
  reconcile/utils/vcs.py,sha256=o1r0n_IrU2El75CED_6sjR2GZGM-exuWsj5F7jONaMU,6779
591
592
  reconcile/utils/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
592
593
  reconcile/utils/acs/base.py,sha256=Qih-xZ3RBJZEE291iHHlv7lUY6ShcAvSj1PA3_aTTnM,2276
593
- reconcile/utils/acs/policies.py,sha256=utDmFKb6pbBN3W6JFxRrlr9yFwL3aQurGDywaFGM6w0,5196
594
+ reconcile/utils/acs/policies.py,sha256=_jAz6cv8KRYtDsXjGoJgNbD8_9PUa5LSwwVlpK4A_cQ,5505
594
595
  reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
595
596
  reconcile/utils/cloud_resource_best_practice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
596
597
  reconcile/utils/cloud_resource_best_practice/aws_rds.py,sha256=EvE6XKLsrZ531MJptKqPht2lOETrOjySTHXk6CzMgo0,2279
@@ -657,7 +658,7 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
657
658
  tools/app_interface_reporter.py,sha256=upA-J-n-HXHKVDINRuMR7vTt-iJvQORKUVi9D3leQto,17738
658
659
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
659
660
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
660
- tools/qontract_cli.py,sha256=bw7JVS9zaob2hvb-0MkGovTEK5HnxAy5ms-MEAU9WQU,103543
661
+ tools/qontract_cli.py,sha256=mN5oXM_lmFmCGwoGU77qqO8nzlfKnNGlP6y-ILFyHIQ,103423
661
662
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
662
663
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
663
664
  tools/cli_commands/gpg_encrypt.py,sha256=w8hl4jIEWk5wKbEFN6fVEOwUJGmdlvOqYodW3XSN7mU,4978
@@ -665,11 +666,11 @@ tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOy
665
666
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
666
667
  tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
667
668
  tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvftCWEEf-g1mfXOtgCog-g,1271
668
- tools/test/test_qontract_cli.py,sha256=d18KrdhtUGqoC7_kWZU128U0-VJEj-0rjFkLVufcI6I,2755
669
+ tools/test/test_qontract_cli.py,sha256=se-YG_YVCWRFrnCPvBVHDBT_59CkbIoEni-4SJa8_MU,2755
669
670
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
670
671
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
671
- qontract_reconcile-0.10.1rc536.dist-info/METADATA,sha256=BZPcvTsEmz0tR4NJmj-JZM7l_J2qAMSglos5FuteOCo,2349
672
- qontract_reconcile-0.10.1rc536.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
673
- qontract_reconcile-0.10.1rc536.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
674
- qontract_reconcile-0.10.1rc536.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
675
- qontract_reconcile-0.10.1rc536.dist-info/RECORD,,
672
+ qontract_reconcile-0.10.1rc538.dist-info/METADATA,sha256=TkmlNiYqcISDkpP1_R0KR4m709lpVpeeJUAkfjVEFVc,2349
673
+ qontract_reconcile-0.10.1rc538.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
674
+ qontract_reconcile-0.10.1rc538.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
675
+ qontract_reconcile-0.10.1rc538.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
676
+ qontract_reconcile-0.10.1rc538.dist-info/RECORD,,
reconcile/acs_policies.py CHANGED
@@ -58,7 +58,10 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
58
58
  return self.qontract_integration.replace("_", "-")
59
59
 
60
60
  def _build_policy(
61
- self, gql_policy: AcsPolicyV1, notifier_name_to_id: dict[str, str]
61
+ self,
62
+ gql_policy: AcsPolicyV1,
63
+ notifier_name_to_id: dict[str, str],
64
+ cluster_name_to_id: dict[str, str],
62
65
  ) -> Policy:
63
66
  conditions = [
64
67
  pc for c in gql_policy.conditions if (pc := self._build_policy_condition(c))
@@ -72,7 +75,7 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
72
75
  severity=f"{gql_policy.severity.upper()}_SEVERITY", # align with acs api severity value format
73
76
  scope=sorted(
74
77
  [
75
- Scope(cluster=cs.name, namespace="")
78
+ Scope(cluster=cluster_name_to_id[cs.name], namespace="")
76
79
  for cs in cast(
77
80
  gql_acs_policies.AcsPolicyScopeClusterV1,
78
81
  gql_policy.scope,
@@ -83,7 +86,9 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
83
86
  if gql_policy.scope.level == "cluster"
84
87
  else sorted(
85
88
  [
86
- Scope(cluster=ns.cluster.name, namespace=ns.name)
89
+ Scope(
90
+ cluster=cluster_name_to_id[ns.cluster.name], namespace=ns.name
91
+ )
87
92
  for ns in cast(
88
93
  gql_acs_policies.AcsPolicyScopeNamespaceV1,
89
94
  gql_policy.scope,
@@ -158,7 +163,10 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
158
163
  return None
159
164
 
160
165
  def get_desired_state(
161
- self, query_func: Callable, notifiers: list[AcsPolicyApi.NotifierIdentifiers]
166
+ self,
167
+ query_func: Callable,
168
+ notifiers: list[AcsPolicyApi.NotifierIdentifiers],
169
+ clusters: list[AcsPolicyApi.ClusterIdentifiers],
162
170
  ) -> list[Policy]:
163
171
  """
164
172
  Get desired ACS security policies and convert to acs api policy object format
@@ -167,8 +175,9 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
167
175
  :return: list of utils.acs.policies.Policy derived from acs-policy-1 definitions
168
176
  """
169
177
  notifier_name_to_id = {n.name: n.id for n in notifiers}
178
+ cluster_name_to_id = {c.name: c.id for c in clusters}
170
179
  return [
171
- self._build_policy(gql_policy, notifier_name_to_id)
180
+ self._build_policy(gql_policy, notifier_name_to_id, cluster_name_to_id)
172
181
  for gql_policy in gql_acs_policies.query(query_func=query_func).acs_policies
173
182
  or []
174
183
  ]
@@ -225,7 +234,8 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
225
234
  instance={"url": instance.url, "token": token[instance.credentials.field]}
226
235
  ) as acs_api:
227
236
  notifiers = acs_api.list_notifiers()
228
- desired = self.get_desired_state(gqlapi.query, notifiers)
237
+ clusters = acs_api.list_clusters()
238
+ desired = self.get_desired_state(gqlapi.query, notifiers, clusters)
229
239
  current = acs_api.get_custom_policies()
230
240
  self.reconcile(
231
241
  desired=desired, current=current, acs=acs_api, dry_run=dry_run
reconcile/cli.py CHANGED
@@ -507,6 +507,30 @@ def trigger_integration(function):
507
507
  return function
508
508
 
509
509
 
510
+ def enable_extended_early_exit(function):
511
+ return click.option(
512
+ "--enable-extended-early-exit/--no-enable-extended-early-exit",
513
+ default=False,
514
+ help="enable extended early exit.",
515
+ )(function)
516
+
517
+
518
+ def extended_early_exit_cache_ttl_seconds(function):
519
+ return click.option(
520
+ "--extended-early-exit-cache-ttl-seconds",
521
+ default=3600,
522
+ help="TTL of extended early exit cache in seconds.",
523
+ )(function)
524
+
525
+
526
+ def log_cached_log_output(function):
527
+ return click.option(
528
+ "--log-cached-log-output/--no-log-cached-log-output",
529
+ default=False,
530
+ help="log the cached log output.",
531
+ )(function)
532
+
533
+
510
534
  def register_faulthandler(fileobj=sys.__stderr__):
511
535
  if fileobj:
512
536
  if not faulthandler.is_enabled():
@@ -1738,6 +1762,9 @@ def terraform_repo(ctx, output_file, gitlab_project_id, gitlab_merge_request_id)
1738
1762
  @enable_deletion(default=False)
1739
1763
  @account_name_multiple
1740
1764
  @exclude_aws_accounts
1765
+ @enable_extended_early_exit
1766
+ @extended_early_exit_cache_ttl_seconds
1767
+ @log_cached_log_output
1741
1768
  @click.option(
1742
1769
  "--light/--full",
1743
1770
  default=False,
@@ -1755,6 +1782,9 @@ def terraform_resources(
1755
1782
  vault_output_path,
1756
1783
  account_name,
1757
1784
  exclude_accounts,
1785
+ enable_extended_early_exit,
1786
+ extended_early_exit_cache_ttl_seconds,
1787
+ log_cached_log_output,
1758
1788
  ):
1759
1789
  import reconcile.terraform_resources
1760
1790
 
@@ -1772,6 +1802,9 @@ def terraform_resources(
1772
1802
  vault_output_path,
1773
1803
  account_name=account_name,
1774
1804
  exclude_accounts=exclude_accounts,
1805
+ enable_extended_early_exit=enable_extended_early_exit,
1806
+ extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
1807
+ log_cached_log_output=log_cached_log_output,
1775
1808
  )
1776
1809
 
1777
1810
 
@@ -1,6 +1,4 @@
1
1
  import logging
2
- import shutil
3
- import sys
4
2
  from collections.abc import (
5
3
  Callable,
6
4
  Iterable,
@@ -11,6 +9,7 @@ from typing import (
11
9
  Collection,
12
10
  Optional,
13
11
  Sequence,
12
+ TypedDict,
14
13
  cast,
15
14
  )
16
15
 
@@ -33,6 +32,10 @@ from reconcile.typed_queries.terraform_namespaces import get_namespaces
33
32
  from reconcile.utils import gql
34
33
  from reconcile.utils.aws_api import AWSApi
35
34
  from reconcile.utils.defer import defer
35
+ from reconcile.utils.extended_early_exit import (
36
+ ExtendedEarlyExitRunnerResult,
37
+ extended_early_exit_run,
38
+ )
36
39
  from reconcile.utils.external_resource_spec import (
37
40
  ExternalResourceSpec,
38
41
  ExternalResourceSpecInventory,
@@ -52,10 +55,12 @@ from reconcile.utils.ocm import OCMMap
52
55
  from reconcile.utils.openshift_resource import OpenshiftResource as OR
53
56
  from reconcile.utils.openshift_resource import ResourceInventory
54
57
  from reconcile.utils.runtime.integration import DesiredStateShardConfig
55
- from reconcile.utils.secret_reader import create_secret_reader
58
+ from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
56
59
  from reconcile.utils.semver_helper import make_semver
57
60
  from reconcile.utils.terraform_client import TerraformClient as Terraform
61
+ from reconcile.utils.terrascript_aws_client import TerrascriptClient
58
62
  from reconcile.utils.terrascript_aws_client import TerrascriptClient as Terrascript
63
+ from reconcile.utils.unleash import get_feature_toggle_state
59
64
  from reconcile.utils.vault import (
60
65
  VaultClient,
61
66
  _VaultClient,
@@ -117,18 +122,14 @@ def populate_oc_resources(
117
122
 
118
123
 
119
124
  def fetch_current_state(
120
- dry_run: bool,
121
125
  namespaces: Iterable[NamespaceV1],
122
126
  thread_pool_size: int,
123
127
  internal: Optional[bool],
124
128
  use_jump_host: bool,
125
129
  account_names: Optional[Iterable[str]],
126
- ) -> tuple[ResourceInventory, Optional[OCMap]]:
130
+ secret_reader: SecretReaderBase,
131
+ ) -> tuple[ResourceInventory, OCMap]:
127
132
  ri = ResourceInventory()
128
- if dry_run:
129
- return ri, None
130
- vault_settings = get_app_interface_vault_settings()
131
- secret_reader = create_secret_reader(use_vault=vault_settings.vault)
132
133
  oc_map = init_oc_map_from_namespaces(
133
134
  namespaces=namespaces,
134
135
  integration=QONTRACT_INTEGRATION,
@@ -172,64 +173,74 @@ def init_working_dirs(
172
173
 
173
174
 
174
175
  def filter_accounts_by_name(
175
- accounts: Iterable[Mapping[str, Any]], filter: Iterable[str]
176
- ) -> Collection[Mapping[str, Any]]:
177
- return [ac for ac in accounts if ac["name"] in filter]
176
+ accounts: Iterable[dict[str, Any]], names: Iterable[str]
177
+ ) -> list[dict[str, Any]]:
178
+ return [ac for ac in accounts if ac["name"] in names]
178
179
 
179
180
 
180
181
  def exclude_accounts_by_name(
181
- accounts: Iterable[Mapping[str, Any]], filter: Iterable[str]
182
- ) -> Collection[Mapping[str, Any]]:
183
- return [ac for ac in accounts if ac["name"] not in filter]
182
+ accounts: Iterable[dict[str, Any]], names: Iterable[str]
183
+ ) -> list[dict[str, Any]]:
184
+ return [ac for ac in accounts if ac["name"] not in names]
184
185
 
185
186
 
186
187
  def validate_account_names(
187
188
  accounts: Collection[Mapping[str, Any]], names: Collection[str]
188
189
  ) -> None:
189
- if len(accounts) != len(names):
190
- missing_names = set(names) - {a["name"] for a in accounts}
190
+ if missing_names := set(names) - {a["name"] for a in accounts}:
191
191
  raise ValueError(
192
192
  f"Accounts {missing_names} were provided as arguments, but not found in app-interface. Check your input for typos or for missing AWS account definitions."
193
193
  )
194
194
 
195
195
 
196
- def setup(
196
+ def get_aws_accounts(
197
197
  dry_run: bool,
198
- print_to_file: Optional[str],
199
- thread_pool_size: int,
200
- internal: Optional[bool],
201
- use_jump_host: bool,
202
198
  include_accounts: Optional[Collection[str]],
203
199
  exclude_accounts: Optional[Collection[str]],
204
- ) -> tuple[
205
- ResourceInventory, Optional[OCMap], Terraform, ExternalResourceSpecInventory
206
- ]:
200
+ ) -> list[dict[str, Any]]:
201
+ if exclude_accounts and not dry_run:
202
+ message = "--exclude-accounts is only supported in dry-run mode"
203
+ logging.error(message)
204
+ raise ExcludeAccountsAndDryRunException(message)
205
+
206
+ if exclude_accounts and include_accounts:
207
+ message = "Using --exclude-accounts and --account-name at the same time is not allowed"
208
+ logging.error(message)
209
+ raise ExcludeAccountsAndAccountNameException(message)
210
+
211
+ # If we are not running in dry run we don't want to run with more than one account
212
+ if include_accounts and len(include_accounts) > 1 and not dry_run:
213
+ message = "Running with multiple accounts is only supported in dry-run mode"
214
+ logging.error(message)
215
+ raise MultipleAccountNamesInDryRunException(message)
216
+
207
217
  accounts = queries.get_aws_accounts(terraform_state=True)
208
- if not include_accounts and exclude_accounts:
209
- excluding = filter_accounts_by_name(accounts, exclude_accounts)
210
- validate_account_names(excluding, exclude_accounts)
211
- accounts = exclude_accounts_by_name(accounts, exclude_accounts)
212
- if len(accounts) == 0:
218
+
219
+ if exclude_accounts:
220
+ validate_account_names(accounts, exclude_accounts)
221
+ filtered_accounts = exclude_accounts_by_name(accounts, exclude_accounts)
222
+ if not filtered_accounts:
213
223
  raise ValueError("You have excluded all aws accounts, verify your input")
214
- account_names = tuple(ac["name"] for ac in accounts)
215
- elif include_accounts:
216
- accounts = filter_accounts_by_name(accounts, include_accounts)
224
+ return filtered_accounts
225
+
226
+ if include_accounts:
217
227
  validate_account_names(accounts, include_accounts)
218
- account_names = tuple(a["name"] for a in accounts)
219
- settings = queries.get_app_interface_settings()
228
+ return filter_accounts_by_name(accounts, include_accounts)
220
229
 
221
- # build a resource inventory for all the kube secrets managed by the
222
- # app-interface managed terraform resources
223
- tf_namespaces = get_tf_namespaces(account_names)
224
- if not tf_namespaces:
225
- logging.warning(
226
- "No terraform namespaces found, consider disabling this integration, account names: "
227
- f"{', '.join(account_names)}"
228
- )
229
- ri, oc_map = fetch_current_state(
230
- dry_run, tf_namespaces, thread_pool_size, internal, use_jump_host, account_names
231
- )
230
+ return accounts
231
+
232
+
233
+ def setup(
234
+ accounts: list[dict[str, Any]],
235
+ account_names: set[str],
236
+ tf_namespaces: list[NamespaceV1],
237
+ print_to_file: Optional[str],
238
+ thread_pool_size: int,
239
+ ) -> tuple[Terraform, TerrascriptClient, SecretReaderBase]:
240
+ vault_settings = get_app_interface_vault_settings()
241
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
232
242
 
243
+ settings = queries.get_app_interface_settings()
233
244
  # initialize terrascript (scripting engine to generate terraform manifests)
234
245
  ts, working_dirs = init_working_dirs(accounts, thread_pool_size, settings=settings)
235
246
 
@@ -260,7 +271,7 @@ def setup(
260
271
  ts.populate_resources(ocm_map=ocm_map)
261
272
  ts.dump(print_to_file, existing_dirs=working_dirs)
262
273
 
263
- return ri, oc_map, tf, ts.resource_spec_inventory
274
+ return tf, ts, secret_reader
264
275
 
265
276
 
266
277
  def filter_tf_namespaces(
@@ -290,21 +301,6 @@ def filter_tf_namespaces(
290
301
  return tf_namespaces
291
302
 
292
303
 
293
- def cleanup_and_exit(
294
- tf: Optional[Terraform] = None,
295
- status: bool = False,
296
- working_dirs: Optional[Mapping[str, str]] = None,
297
- ) -> None:
298
- if working_dirs is None:
299
- working_dirs = {}
300
- if tf is None:
301
- for wd in working_dirs.values():
302
- shutil.rmtree(wd)
303
- else:
304
- tf.cleanup()
305
- sys.exit(status)
306
-
307
-
308
304
  @retry()
309
305
  def write_outputs_to_vault(
310
306
  vault_path: str, resource_specs: ExternalResourceSpecInventory
@@ -366,65 +362,125 @@ def run(
366
362
  vault_output_path: str = "",
367
363
  account_name: Optional[Sequence[str]] = None,
368
364
  exclude_accounts: Optional[Sequence[str]] = None,
365
+ enable_extended_early_exit: bool = False,
366
+ extended_early_exit_cache_ttl_seconds: int = 3600,
367
+ log_cached_log_output: bool = False,
369
368
  defer: Optional[Callable] = None,
370
369
  ) -> None:
371
- if exclude_accounts and not dry_run:
372
- message = "--exclude-accounts is only supported in dry-run mode"
373
- logging.error(message)
374
- raise ExcludeAccountsAndDryRunException(message)
375
-
376
- if exclude_accounts and account_name:
377
- message = "Using --exclude-accounts and --account-name at the same time is not allowed"
378
- logging.error(message)
379
- raise ExcludeAccountsAndAccountNameException(message)
380
-
381
370
  # account_name is a tuple of account names for more detail go to
382
371
  # https://click.palletsprojects.com/en/8.1.x/options/#multiple-options
383
- account_names = account_name
384
-
385
- # acc_name will prevent type error since account_name is not a str
386
- acc_name: Optional[str] = account_names[0] if account_names else None
387
-
388
- # If we are not running in dry run we don't want to run with more than one account
389
- if account_names and len(account_names) > 1 and not dry_run:
390
- message = "Running with multiple accounts is only supported in dry-run mode"
391
- logging.error(message)
392
- raise MultipleAccountNamesInDryRunException(message)
372
+ accounts = get_aws_accounts(dry_run, account_name, exclude_accounts)
373
+ account_names = {a["name"] for a in accounts}
374
+ tf_namespaces = get_tf_namespaces(account_names)
375
+ if not tf_namespaces:
376
+ logging.warning(
377
+ "No terraform namespaces found, consider disabling this integration, account names: "
378
+ f"{', '.join(account_names)}"
379
+ )
393
380
 
394
- ri, oc_map, tf, resource_specs = setup(
395
- dry_run,
381
+ tf, ts, secret_reader = setup(
382
+ accounts,
383
+ account_names,
384
+ tf_namespaces,
396
385
  print_to_file,
397
386
  thread_pool_size,
398
- internal,
399
- use_jump_host,
400
- account_names,
401
- exclude_accounts,
402
387
  )
403
- publish_metrics(resource_specs, QONTRACT_INTEGRATION)
388
+ if defer:
389
+ defer(tf.cleanup)
404
390
 
405
- if not dry_run and oc_map and defer:
406
- defer(oc_map.cleanup)
391
+ publish_metrics(ts.resource_spec_inventory, QONTRACT_INTEGRATION)
407
392
 
408
393
  if print_to_file:
409
- cleanup_and_exit(tf)
410
- if tf is None:
411
- err = True
412
- cleanup_and_exit(tf, err)
394
+ return
395
+
396
+ runner_params: RunnerParams = dict(
397
+ accounts=accounts,
398
+ account_names=account_names,
399
+ tf_namespaces=tf_namespaces,
400
+ tf=tf,
401
+ ts=ts,
402
+ secret_reader=secret_reader,
403
+ dry_run=dry_run,
404
+ enable_deletion=enable_deletion,
405
+ thread_pool_size=thread_pool_size,
406
+ internal=internal,
407
+ use_jump_host=use_jump_host,
408
+ light=light,
409
+ vault_output_path=vault_output_path,
410
+ defer=defer,
411
+ )
413
412
 
413
+ if enable_extended_early_exit and get_feature_toggle_state(
414
+ "terraform-resources-extended-early-exit",
415
+ default=False,
416
+ ):
417
+ extended_early_exit_run(
418
+ integration=QONTRACT_INTEGRATION,
419
+ integration_version=QONTRACT_INTEGRATION_VERSION,
420
+ dry_run=dry_run,
421
+ cache_source=ts.terraform_configurations(),
422
+ ttl_seconds=extended_early_exit_cache_ttl_seconds,
423
+ logger=logging.getLogger(),
424
+ runner=runner,
425
+ runner_params=runner_params,
426
+ secret_reader=secret_reader,
427
+ log_cached_log_output=log_cached_log_output,
428
+ )
429
+ else:
430
+ runner(**runner_params)
431
+
432
+
433
+ class RunnerParams(TypedDict):
434
+ accounts: list[dict[str, Any]]
435
+ account_names: set[str]
436
+ tf_namespaces: list[NamespaceV1]
437
+ tf: Terraform
438
+ ts: Terrascript
439
+ secret_reader: SecretReaderBase
440
+ dry_run: bool
441
+ enable_deletion: bool
442
+ thread_pool_size: int
443
+ internal: Optional[bool]
444
+ use_jump_host: bool
445
+ light: bool
446
+ vault_output_path: str
447
+ defer: Optional[Callable]
448
+
449
+
450
+ def runner(
451
+ accounts: list[dict[str, Any]],
452
+ account_names: set[str],
453
+ tf_namespaces: list[NamespaceV1],
454
+ tf: Terraform,
455
+ ts: Terrascript,
456
+ secret_reader: SecretReaderBase,
457
+ dry_run: bool,
458
+ enable_deletion: bool = False,
459
+ thread_pool_size: int = 10,
460
+ internal: Optional[bool] = None,
461
+ use_jump_host: bool = True,
462
+ light: bool = False,
463
+ vault_output_path: str = "",
464
+ defer: Optional[Callable] = None,
465
+ ) -> ExtendedEarlyExitRunnerResult:
414
466
  if not light:
415
467
  disabled_deletions_detected, err = tf.plan(enable_deletion)
416
468
  if err:
417
- cleanup_and_exit(tf, err)
469
+ raise RuntimeError("Terraform plan has errors")
418
470
  if disabled_deletions_detected:
419
- cleanup_and_exit(tf, disabled_deletions_detected)
471
+ raise RuntimeError("Terraform plan has disabled deletions detected")
420
472
 
421
473
  if dry_run:
422
- cleanup_and_exit(tf)
474
+ return ExtendedEarlyExitRunnerResult(
475
+ payload=ts.terraform_configurations(),
476
+ applied_count=0,
477
+ )
423
478
 
424
- if not light and tf.should_apply:
479
+ acc_name = accounts[0]["name"] if accounts else None
480
+ if not light and tf.should_apply():
425
481
  err = tf.apply()
426
482
  if err:
427
- cleanup_and_exit(tf, err)
483
+ raise RuntimeError("Terraform apply has errors")
428
484
 
429
485
  if defer:
430
486
  defer(
@@ -437,26 +493,41 @@ def run(
437
493
 
438
494
  # refresh output data after terraform apply
439
495
  tf.populate_terraform_output_secrets(
440
- resource_specs=resource_specs, init_rds_replica_source=True
496
+ resource_specs=ts.resource_spec_inventory, init_rds_replica_source=True
441
497
  )
498
+
499
+ ri, oc_map = fetch_current_state(
500
+ tf_namespaces,
501
+ thread_pool_size,
502
+ internal,
503
+ use_jump_host,
504
+ account_names,
505
+ secret_reader=secret_reader,
506
+ )
507
+ if defer:
508
+ defer(oc_map.cleanup)
442
509
  # populate the resource inventory with latest output data
443
- populate_desired_state(ri, resource_specs)
510
+ populate_desired_state(ri, ts.resource_spec_inventory)
444
511
 
445
512
  ob.publish_metrics(ri, QONTRACT_INTEGRATION)
446
- actions = []
447
- if oc_map:
448
- actions = ob.realize_data(
449
- dry_run, oc_map, ri, thread_pool_size, caller=acc_name
450
- )
513
+ actions = ob.realize_data(
514
+ dry_run,
515
+ oc_map,
516
+ ri,
517
+ thread_pool_size,
518
+ caller=acc_name,
519
+ )
451
520
 
452
521
  if actions and vault_output_path:
453
- write_outputs_to_vault(vault_output_path, resource_specs)
522
+ write_outputs_to_vault(vault_output_path, ts.resource_spec_inventory)
454
523
 
455
524
  if ri.has_error_registered():
456
- err = True
457
- cleanup_and_exit(tf, err)
525
+ raise RuntimeError("Resource inventory has errors registered")
458
526
 
459
- cleanup_and_exit(tf)
527
+ return ExtendedEarlyExitRunnerResult(
528
+ payload=ts.terraform_configurations(),
529
+ applied_count=tf.apply_count + len(actions),
530
+ )
460
531
 
461
532
 
462
533
  def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]: