qontract-reconcile 0.10.1rc585__py3-none-any.whl → 0.10.1rc586__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.
- {qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/RECORD +18 -14
- reconcile/ocm_upgrade_scheduler_org_updater.py +1 -1
- reconcile/openshift_resources_base.py +7 -244
- reconcile/prometheus_rules_tester/integration.py +2 -1
- reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +1 -1
- reconcile/test/test_openshift_resources_base.py +0 -31
- reconcile/test/test_utils_jinja2.py +123 -0
- reconcile/utils/external_resource_spec.py +2 -4
- reconcile/utils/jinja2/__init__.py +0 -0
- reconcile/utils/{jinja2_ext.py → jinja2/extensions.py} +6 -4
- reconcile/utils/jinja2/filters.py +128 -0
- reconcile/utils/jinja2/utils.py +188 -0
- reconcile/utils/saasherder/saasherder.py +1 -1
- reconcile/utils/terrascript_aws_client.py +2 -2
- {qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc586
|
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
|
{qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/RECORD
RENAMED
@@ -59,7 +59,7 @@ reconcile/ocm_github_idp.py,sha256=glwXMsIBcl38-OmDDQCpe0YoLLXfoRgVQmqwXMEXjds,3
|
|
59
59
|
reconcile/ocm_groups.py,sha256=AmQ61fjJYS5PxwNEWtOAvOoJM86VfRQ0-ic6wgw6PU0,2888
|
60
60
|
reconcile/ocm_machine_pools.py,sha256=eebJ6iiTdUcuKE5zBcfNxW1OGmPOvgBtmVu3xNVOoyY,16608
|
61
61
|
reconcile/ocm_update_recommended_version.py,sha256=IYkfLXIprOW1jguZeELcGP1iBPuj-b53R-FTqKulMl8,4204
|
62
|
-
reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=
|
62
|
+
reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=49Ss6sp9_n5F9914gXb-uEap4Vm2t-KPTJRFFViJMIo,4184
|
63
63
|
reconcile/openshift_base.py,sha256=7aifvl-ay5wpY6encbUX9pGbKdjiwJmevZ3XWGRzpCM,49696
|
64
64
|
reconcile/openshift_cluster_bots.py,sha256=eRPYZqWMKFNxLlSN0QG97V5t1iIESQ0BbGaiaQP5VB0,10940
|
65
65
|
reconcile/openshift_clusterrolebindings.py,sha256=QfSy1Ik8eEY5XObc1Q4xyhqyErZenJmbPv_u9wcDNNo,5864
|
@@ -70,7 +70,7 @@ reconcile/openshift_namespaces.py,sha256=DboMc6t0vXD54lL9ZP9P9fQnCRo2g_0z5FWubtW
|
|
70
70
|
reconcile/openshift_network_policies.py,sha256=_qqv7yj17OM1J8KJPsFmzFZ85gzESJeBocC672z4_WU,4231
|
71
71
|
reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
|
72
72
|
reconcile/openshift_resources.py,sha256=kwsY5cko7udEKNlhL2oKiKv_5wzEw9wmmwROE016ng8,1400
|
73
|
-
reconcile/openshift_resources_base.py,sha256=
|
73
|
+
reconcile/openshift_resources_base.py,sha256=3BnifJYoq7hQvrXjtuBppg36uR_tjm1kF2Q80-Pcbac,39349
|
74
74
|
reconcile/openshift_rolebindings.py,sha256=LlImloBisEqzc36jaatic-TeM3hzqMEfxogF-dM4Yhw,6599
|
75
75
|
reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
|
76
76
|
reconcile/openshift_saas_deploy.py,sha256=fmhopPEbyZsGQHRPzyzpKEvoBXEGN3aPxFi7Utq0emU,12788
|
@@ -346,7 +346,7 @@ reconcile/oum/models.py,sha256=0ZyCnULRxAbIEXX60BkkPZVg53DCD6ZJ6wnNT2ANROM,1743
|
|
346
346
|
reconcile/oum/providers.py,sha256=3kEjXvsTPzXc7gzrdO7hWqgzcMmMZMpk2S0X7wQUTWU,1767
|
347
347
|
reconcile/oum/standalone.py,sha256=bzyV8wz3SrERG9zJRFiJCBzSIGwDNj9sNqUytngDw94,7368
|
348
348
|
reconcile/prometheus_rules_tester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
349
|
-
reconcile/prometheus_rules_tester/integration.py,sha256=
|
349
|
+
reconcile/prometheus_rules_tester/integration.py,sha256=hCJJ0udrvgMM78_gFUGvZWOH8azI2tABQKpQq6lHhY0,9232
|
350
350
|
reconcile/rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
351
351
|
reconcile/rhidp/common.py,sha256=suTh9T4dPOgrKi-rDALgjzCSL9JHGnkTYALFIIjNCJE,6801
|
352
352
|
reconcile/rhidp/metrics.py,sha256=Yp0GtpjhieEdru0qkG3osBTJiKUzg6CAjwPoFTQDnCg,417
|
@@ -427,13 +427,13 @@ reconcile/test/test_ocm_clusters.py,sha256=NOxhJnlgOlRZF0-sfXCiRP0aiNiBiMl35MTCC
|
|
427
427
|
reconcile/test/test_ocm_clusters_manifest_updates.py,sha256=jFRVfc5jby1kI2x_gT6wcqPPgkav1et9wZH6JqQbNSY,3278
|
428
428
|
reconcile/test/test_ocm_machine_pools.py,sha256=3qo6t2Jfr1Wee0NUacyLTDmatp0o7CUNpkVOpHiOiGk,29737
|
429
429
|
reconcile/test/test_ocm_update_recommended_version.py,sha256=iA4BVirTGVXlwcOyeR52IuNO81X_8NR6ZNd7ZFE7igs,4328
|
430
|
-
reconcile/test/test_ocm_upgrade_scheduler_org_updater.py,sha256=
|
430
|
+
reconcile/test/test_ocm_upgrade_scheduler_org_updater.py,sha256=hT6sbdGUx8LGnMVvNI7wOVHcoan1YurckytlvtJdzGk,4347
|
431
431
|
reconcile/test/test_openshift_base.py,sha256=uVsnMghAQhHaJTreeOw4x2INTKJ6qeiZiiteWeKflW8,33874
|
432
432
|
reconcile/test/test_openshift_cluster_bots.py,sha256=L-yuKvMgB0LBCdfLu7wozh_lk6S_m3umXt3m_ECfLEI,8023
|
433
433
|
reconcile/test/test_openshift_namespace_labels.py,sha256=P1hqi6P88NijNrurdXG_QR2usyo3EYZSy9zpwYHvDsM,12104
|
434
434
|
reconcile/test/test_openshift_namespaces.py,sha256=HmRnCE5EnFt3MYceVEFHmk8wWRtCrxu2AFGFkY9pdyA,9214
|
435
435
|
reconcile/test/test_openshift_resource.py,sha256=lbTf48jX1q6rGnRiA5pPvfU0uPfY8zhNylMtryn0sLI,12995
|
436
|
-
reconcile/test/test_openshift_resources_base.py,sha256=
|
436
|
+
reconcile/test/test_openshift_resources_base.py,sha256=LtlR9x3o7KkSEw0JN0fZhinFeAAxBAQlB_9PpBnKwOM,14353
|
437
437
|
reconcile/test/test_openshift_saas_deploy.py,sha256=YLJGkc--u5aP0UkQ-b9ZGEFGS2gw25jjcSgknQdI3Ic,5892
|
438
438
|
reconcile/test/test_openshift_saas_deploy_change_tester.py,sha256=1yVe54Hx9YdVjn6qdnKge5Sa_s732c-8uZqCnuT1gGI,12871
|
439
439
|
reconcile/test/test_openshift_tekton_resources.py,sha256=RtRWsdm51S13OSkENC9nY_rOH0QELSCaO5tjF0XqIDI,11222
|
@@ -465,6 +465,7 @@ reconcile/test/test_terraform_vpc_peerings.py,sha256=ubcsKh0TrUIwuI1-W3ETIgzsFvz
|
|
465
465
|
reconcile/test/test_terraform_vpc_peerings_build_desired_state.py,sha256=DAfpb12I0PlqnuVUHK2vh4LH4d1OylT3H2GE_3TGZZI,47852
|
466
466
|
reconcile/test/test_three_way_diff_strategy.py,sha256=2fjEqE2w4pIzKq18PRcADTSe01aGwsZfMGloU8xfNaE,3346
|
467
467
|
reconcile/test/test_unleash.py,sha256=c1s_FRAZrAzzd3FbZrzHYjJzHELhoxPHBZnEzqsfMQg,6416
|
468
|
+
reconcile/test/test_utils_jinja2.py,sha256=TpzQlpFnLGzNEZp5WOh0o7AuBiGEktqO4MuwiiJW2YY,3895
|
468
469
|
reconcile/test/test_vault_replication.py,sha256=wlc4jm9f8P641UvvxIFFFc5_unJysNkOVrKJscjhQr0,16867
|
469
470
|
reconcile/test/test_vault_utils.py,sha256=vbJnc89XAuE07qbTuWxHM5o9F6R9SO5aHXA38fwxT7A,1122
|
470
471
|
reconcile/test/test_version_bump.py,sha256=q6-3Y1roriI6YWpFwaHOMN7emEP3yL33sh_0VdbmG7E,511
|
@@ -549,7 +550,7 @@ reconcile/utils/environ.py,sha256=VnW3zp6Un_UJn5BU4FU8RfhuqtZp0s-VeuuHnqC_WcQ,51
|
|
549
550
|
reconcile/utils/exceptions.py,sha256=DwfnWUpVOotpP79RWZ2pycmG6nKCL00RBIeZLYkQPW4,635
|
550
551
|
reconcile/utils/expiration.py,sha256=BXwKE50sNIV-Lszke97fxitNkLxYszoOLW1LBgp_yqg,1246
|
551
552
|
reconcile/utils/extended_early_exit.py,sha256=2qvw9W2PW0t4JCZFD8wD3BINWIy02lkvpOrd2YDaYHE,6195
|
552
|
-
reconcile/utils/external_resource_spec.py,sha256=
|
553
|
+
reconcile/utils/external_resource_spec.py,sha256=IRY8MCsyWKzt-Qj_hXiFKgkCvZu6VVyj6IYtqEb05BA,6618
|
553
554
|
reconcile/utils/external_resources.py,sha256=a2CkJ3KLociYBnc_9F2VWfZGWMhzDl6fDNhwo2U-MWU,7501
|
554
555
|
reconcile/utils/filtering.py,sha256=zZnHH0u0SaTDyzuFXZ_mREURGLvjEqQIQy4z-7QBVlc,419
|
555
556
|
reconcile/utils/git.py,sha256=Qad7mfPuS9s7eKODeWSewehwSGgJPCbQuLda1qg_6GA,1522
|
@@ -564,7 +565,6 @@ reconcile/utils/helpers.py,sha256=k9svgFFZG7H5FvHYY0g5jJyvgvh2UDZxf0Ib221teag,11
|
|
564
565
|
reconcile/utils/imap_client.py,sha256=byFAJATbITJPsGECSbvXBOcCnoeTUpDFiEjzOAxLm_U,1975
|
565
566
|
reconcile/utils/instrumented_wrappers.py,sha256=eVwMoa6FCrYxLv3RML3WpZF9qKVfCTjMxphgVXG03OM,1073
|
566
567
|
reconcile/utils/jenkins_api.py,sha256=MyJSB_S3uYf3sXnt9t03-gZNQ7tbdd7Wusv3MoF2fRc,7113
|
567
|
-
reconcile/utils/jinja2_ext.py,sha256=l628RR9r9dAGBWLVegoCbSqnjojeizNGiq9Cstt02nE,1129
|
568
568
|
reconcile/utils/jira_client.py,sha256=f7u3xvI4tk27LOnlZxxG1OjELC5vC6wzlyk3Fo7tDnY,7007
|
569
569
|
reconcile/utils/jjb_client.py,sha256=Pdy0dLCFvD6GPCaC0tZydYgkVJPOxYXIiwWECZaFJBU,14551
|
570
570
|
reconcile/utils/jsonpath.py,sha256=NRpAEijKN4cMDjo7qivNPqpm0__GQQ1TiE0PBEBO45s,5572
|
@@ -601,7 +601,7 @@ reconcile/utils/state.py,sha256=SAa6QLHu9lr0yqLCBy2AypNx1IPCJWlrRBrvlzAKsOU,1450
|
|
601
601
|
reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
|
602
602
|
reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
|
603
603
|
reconcile/utils/terraform_client.py,sha256=_jBriLBwU005bDxWlq7CRByOkVCfiH47oBzB0ArNAY8,31901
|
604
|
-
reconcile/utils/terrascript_aws_client.py,sha256=
|
604
|
+
reconcile/utils/terrascript_aws_client.py,sha256=IQxaDYujdfD-AVH-_RtqklDV-RGRZSjWAZFIkP_6NFI,269716
|
605
605
|
reconcile/utils/three_way_diff_strategy.py,sha256=nyqeQsLCoPI6e16k2CF3b9KNgQLU-rPf5RtfdUfVMwE,4468
|
606
606
|
reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
|
607
607
|
reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,3601
|
@@ -620,6 +620,10 @@ reconcile/utils/glitchtip/models.py,sha256=_oqZXNkyRTsAnx6tF4WUURSBj0cc9UNS4okOQ
|
|
620
620
|
reconcile/utils/internal_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
621
621
|
reconcile/utils/internal_groups/client.py,sha256=abREA8RwXKybXFjCK8CAcCr-iUp2r0tAbIEJ-c-PXws,4538
|
622
622
|
reconcile/utils/internal_groups/models.py,sha256=jlkH_hyyyuwS0J1IpuS7W1AyQSKQ2QpHelXoH36edbE,2316
|
623
|
+
reconcile/utils/jinja2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
624
|
+
reconcile/utils/jinja2/extensions.py,sha256=zV_x8MhSHAynKhFnG3fULXrwsm5fUG_88IygZHSnN0o,1284
|
625
|
+
reconcile/utils/jinja2/filters.py,sha256=_kJjdMsY3lGS5PUn4NnpXUQDNrL1IwiKsB-0MhTMGYM,4521
|
626
|
+
reconcile/utils/jinja2/utils.py,sha256=NW2BLizE-SGudgtdKqlo02CZlNda1W-swVp6uldKtUs,5779
|
623
627
|
reconcile/utils/membershipsources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
624
628
|
reconcile/utils/membershipsources/app_interface_resolver.py,sha256=IlDiRtJZ0AfAGKEawybB6SvsKbm1POTXL6fpEt699E0,1979
|
625
629
|
reconcile/utils/membershipsources/models.py,sha256=IFu6KHFe-HUTJPiAO3fEw7i22yv4_ytgBW-h_wrO6V4,2015
|
@@ -660,7 +664,7 @@ reconcile/utils/runtime/sharding.py,sha256=roCdbnBklhTK_g34zbgQYqzpKPaNQ8J6Xd9XL
|
|
660
664
|
reconcile/utils/saasherder/__init__.py,sha256=J3MBZBFa5YmhqYm08QsjBXz8mFcVOCiOCkyIcw41t7E,343
|
661
665
|
reconcile/utils/saasherder/interfaces.py,sha256=XXY35h8VWQ66z3LBPxaoUAMkIW50264DQiecrzyV6oA,9076
|
662
666
|
reconcile/utils/saasherder/models.py,sha256=PBv8DuAb6KUw_ayn5Ufiya20cCAelBv6Iv--x7hbpa4,5449
|
663
|
-
reconcile/utils/saasherder/saasherder.py,sha256=
|
667
|
+
reconcile/utils/saasherder/saasherder.py,sha256=72b0u-cIHg62R2uQCqlGcQfW5TbCxWDKf0dfmgVMUbY,85733
|
664
668
|
reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
|
665
669
|
reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
|
666
670
|
reconcile/utils/terraform/config_client.py,sha256=py-Ree-QUYD6Hvng6bM40VgSuttteehIKNgwOSoJO1o,4706
|
@@ -688,8 +692,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
688
692
|
tools/test/test_qontract_cli.py,sha256=EzJwaPxQw5CLPahLjh91oRT0pi1WCgOXZ_KgiNXZMAw,2948
|
689
693
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
690
694
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
691
|
-
qontract_reconcile-0.10.
|
692
|
-
qontract_reconcile-0.10.
|
693
|
-
qontract_reconcile-0.10.
|
694
|
-
qontract_reconcile-0.10.
|
695
|
-
qontract_reconcile-0.10.
|
695
|
+
qontract_reconcile-0.10.1rc586.dist-info/METADATA,sha256=i0GRb6YD41fyHubkOLfO3pHuPskCUvJP2_T3dpYlHlk,2349
|
696
|
+
qontract_reconcile-0.10.1rc586.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
697
|
+
qontract_reconcile-0.10.1rc586.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
|
698
|
+
qontract_reconcile-0.10.1rc586.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
699
|
+
qontract_reconcile-0.10.1rc586.dist-info/RECORD,,
|
@@ -12,7 +12,7 @@ from reconcile import (
|
|
12
12
|
mr_client_gateway,
|
13
13
|
queries,
|
14
14
|
)
|
15
|
-
from reconcile.
|
15
|
+
from reconcile.utils.jinja2.utils import process_jinja2_template
|
16
16
|
from reconcile.utils.ocm import (
|
17
17
|
OCMMap,
|
18
18
|
OCMSpec,
|
@@ -13,7 +13,6 @@ from collections.abc import (
|
|
13
13
|
)
|
14
14
|
from contextlib import contextmanager
|
15
15
|
from dataclasses import dataclass
|
16
|
-
from functools import cache
|
17
16
|
from textwrap import indent
|
18
17
|
from threading import Lock
|
19
18
|
from typing import (
|
@@ -22,14 +21,10 @@ from typing import (
|
|
22
21
|
Protocol,
|
23
22
|
Tuple,
|
24
23
|
)
|
25
|
-
from urllib import parse
|
26
24
|
|
27
25
|
import anymarkup
|
28
|
-
import jinja2
|
29
|
-
import jinja2.sandbox
|
30
26
|
from deepdiff import DeepHash
|
31
27
|
from sretoolbox.utils import (
|
32
|
-
retry,
|
33
28
|
threaded,
|
34
29
|
)
|
35
30
|
|
@@ -37,7 +32,6 @@ import reconcile.openshift_base as ob
|
|
37
32
|
from reconcile import queries
|
38
33
|
from reconcile.change_owners.diff import IDENTIFIER_FIELD_NAME
|
39
34
|
from reconcile.checkpoint import url_makes_sense
|
40
|
-
from reconcile.github_users import init_github
|
41
35
|
from reconcile.utils import (
|
42
36
|
amtool,
|
43
37
|
gql,
|
@@ -45,9 +39,12 @@ from reconcile.utils import (
|
|
45
39
|
)
|
46
40
|
from reconcile.utils.defer import defer
|
47
41
|
from reconcile.utils.exceptions import FetchResourceError
|
48
|
-
from reconcile.utils.
|
49
|
-
|
50
|
-
|
42
|
+
from reconcile.utils.jinja2.utils import (
|
43
|
+
FetchSecretError,
|
44
|
+
lookup_github_file_content,
|
45
|
+
lookup_secret,
|
46
|
+
process_extracurlyjinja2_template,
|
47
|
+
process_jinja2_template,
|
51
48
|
)
|
52
49
|
from reconcile.utils.oc import (
|
53
50
|
OC_Map,
|
@@ -64,7 +61,7 @@ from reconcile.utils.openshift_resource import (
|
|
64
61
|
)
|
65
62
|
from reconcile.utils.openshift_resource import OpenshiftResource as OR
|
66
63
|
from reconcile.utils.runtime.integration import DesiredStateShardConfig
|
67
|
-
from reconcile.utils.secret_reader import
|
64
|
+
from reconcile.utils.secret_reader import SecretReader
|
68
65
|
from reconcile.utils.semver_helper import make_semver
|
69
66
|
from reconcile.utils.sharding import is_in_shard
|
70
67
|
from reconcile.utils.vault import (
|
@@ -254,21 +251,11 @@ def _locked_error_log(msg: str):
|
|
254
251
|
logging.error(msg)
|
255
252
|
|
256
253
|
|
257
|
-
class FetchSecretError(Exception):
|
258
|
-
def __init__(self, msg):
|
259
|
-
super().__init__("error fetching secret: " + str(msg))
|
260
|
-
|
261
|
-
|
262
254
|
class FetchRouteError(Exception):
|
263
255
|
def __init__(self, msg):
|
264
256
|
super().__init__("error fetching route: " + str(msg))
|
265
257
|
|
266
258
|
|
267
|
-
class Jinja2TemplateError(Exception):
|
268
|
-
def __init__(self, msg):
|
269
|
-
super().__init__("error processing jinja2 template: " + str(msg))
|
270
|
-
|
271
|
-
|
272
259
|
class ResourceTemplateRenderError(Exception):
|
273
260
|
pass
|
274
261
|
|
@@ -287,230 +274,6 @@ class UnknownTemplateTypeError(Exception):
|
|
287
274
|
super().__init__("unknown template type error: " + str(msg))
|
288
275
|
|
289
276
|
|
290
|
-
@retry()
|
291
|
-
def lookup_secret(
|
292
|
-
path,
|
293
|
-
key,
|
294
|
-
version=None,
|
295
|
-
tvars=None,
|
296
|
-
allow_not_found=False,
|
297
|
-
settings=None,
|
298
|
-
secret_reader=None,
|
299
|
-
):
|
300
|
-
if tvars is not None:
|
301
|
-
path = process_jinja2_template(
|
302
|
-
body=path, vars=tvars, settings=settings, secret_reader=secret_reader
|
303
|
-
)
|
304
|
-
key = process_jinja2_template(
|
305
|
-
body=key, vars=tvars, settings=settings, secret_reader=secret_reader
|
306
|
-
)
|
307
|
-
if version and not isinstance(version, int):
|
308
|
-
version = process_jinja2_template(
|
309
|
-
body=version, vars=tvars, settings=settings, secret_reader=secret_reader
|
310
|
-
)
|
311
|
-
secret = {"path": path, "field": key, "version": version}
|
312
|
-
try:
|
313
|
-
if not secret_reader:
|
314
|
-
secret_reader = SecretReader(settings)
|
315
|
-
return secret_reader.read(secret)
|
316
|
-
except SecretNotFound as e:
|
317
|
-
if allow_not_found:
|
318
|
-
return None
|
319
|
-
raise FetchSecretError(e)
|
320
|
-
except Exception as e:
|
321
|
-
raise FetchSecretError(e)
|
322
|
-
|
323
|
-
|
324
|
-
def lookup_github_file_content(
|
325
|
-
repo, path, ref, tvars=None, settings=None, secret_reader=None
|
326
|
-
):
|
327
|
-
if tvars is not None:
|
328
|
-
repo = process_jinja2_template(
|
329
|
-
body=repo, vars=tvars, settings=settings, secret_reader=secret_reader
|
330
|
-
)
|
331
|
-
path = process_jinja2_template(
|
332
|
-
body=path, vars=tvars, settings=settings, secret_reader=secret_reader
|
333
|
-
)
|
334
|
-
ref = process_jinja2_template(
|
335
|
-
body=ref, vars=tvars, settings=settings, secret_reader=secret_reader
|
336
|
-
)
|
337
|
-
|
338
|
-
gh = init_github()
|
339
|
-
c = gh.get_repo(repo).get_contents(path, ref).decoded_content
|
340
|
-
return c.decode("utf-8")
|
341
|
-
|
342
|
-
|
343
|
-
def lookup_graphql_query_results(query: str, **kwargs) -> list[Any]:
|
344
|
-
gqlapi = gql.get_api()
|
345
|
-
resource = gqlapi.get_resource(query)["content"]
|
346
|
-
rendered_resource = jinja2.Template(resource).render(**kwargs)
|
347
|
-
results = list(gqlapi.query(rendered_resource).values())[0]
|
348
|
-
return results
|
349
|
-
|
350
|
-
|
351
|
-
def hash_list(input: Iterable) -> str:
|
352
|
-
"""
|
353
|
-
Deterministic hash of a list for jinja2 templates.
|
354
|
-
The order of the list doesn't matter as it is sorted
|
355
|
-
before hashing. Note, that the list elements
|
356
|
-
must be flat primitives (no dicts/lists).
|
357
|
-
"""
|
358
|
-
lst = list(input)
|
359
|
-
str_lst = []
|
360
|
-
for el in lst:
|
361
|
-
if isinstance(el, (list, dict)):
|
362
|
-
raise RuntimeError(
|
363
|
-
f"jinja2 hash_list function received non-primitive value {el}. All values received {lst}"
|
364
|
-
)
|
365
|
-
str_lst.append(str(el))
|
366
|
-
msg = "a" # keep non-empty for hashing empty list
|
367
|
-
msg += "".join(sorted(str_lst))
|
368
|
-
m = hashlib.sha256()
|
369
|
-
m.update(msg.encode("utf-8"))
|
370
|
-
return m.hexdigest()
|
371
|
-
|
372
|
-
|
373
|
-
def eval_filter(input, **kwargs) -> str:
|
374
|
-
"""Jinja2 filter be used when the string
|
375
|
-
is in itself a jinja2 template that must be
|
376
|
-
evaluated with kwargs. For example in the case
|
377
|
-
of the slo-document expression fields.
|
378
|
-
:param input: template string
|
379
|
-
:kwargs: variables that will be used to evaluate the
|
380
|
-
input string
|
381
|
-
:return: rendered string
|
382
|
-
"""
|
383
|
-
return jinja2.Template(input).render(**kwargs)
|
384
|
-
|
385
|
-
|
386
|
-
def json_to_dict(input):
|
387
|
-
"""Jinja2 filter to parse JSON strings into dictionaries.
|
388
|
-
This becomes useful to access Graphql queries data (labels)
|
389
|
-
:param input: json string
|
390
|
-
:return: dict with the parsed inputs contents
|
391
|
-
"""
|
392
|
-
data = json.loads(input)
|
393
|
-
return data
|
394
|
-
|
395
|
-
|
396
|
-
def urlescape(
|
397
|
-
string: str,
|
398
|
-
safe: str = "/",
|
399
|
-
encoding: Optional[str] = None,
|
400
|
-
) -> str:
|
401
|
-
"""Jinja2 filter that is a simple wrapper around urllib's URL quoting
|
402
|
-
functions that takes a string value and makes it safe for use as URL
|
403
|
-
components escaping any reserved characters using URL encoding. See:
|
404
|
-
urllib.parse.quote() and urllib.parse.quote_plus() for reference.
|
405
|
-
|
406
|
-
:param str string: String value to escape.
|
407
|
-
:param str safe: Optional characters that should not be escaped.
|
408
|
-
:param encoding: Encoding to apply to the string to be escaped. Defaults
|
409
|
-
to UTF-8. Unsupported characters raise a UnicodeEncodeError error.
|
410
|
-
:type encoding: typing.Optional[str]
|
411
|
-
:returns: A string with reserved characters escaped.
|
412
|
-
:rtype: str
|
413
|
-
"""
|
414
|
-
return parse.quote(string, safe=safe, encoding=encoding)
|
415
|
-
|
416
|
-
|
417
|
-
def urlunescape(string: str, encoding: Optional[str] = None) -> str:
|
418
|
-
"""Jinja2 filter that is a simple wrapper around urllib's URL unquoting
|
419
|
-
functions that takes an URL-encoded string value and unescapes it
|
420
|
-
replacing any URL-encoded values with their character equivalent. See:
|
421
|
-
urllib.parse.unquote() and urllib.parse.unquote_plus() for reference.
|
422
|
-
|
423
|
-
:param str string: String value to unescape.
|
424
|
-
:param encoding: Encoding to apply to the string to be unescaped. Defaults
|
425
|
-
to UTF-8. Unsupported characters are replaced by placeholder values.
|
426
|
-
:type encoding: typing.Optional[str]
|
427
|
-
:returns: A string with URL-encoded sequences unescaped.
|
428
|
-
:rtype: str
|
429
|
-
"""
|
430
|
-
if encoding is None:
|
431
|
-
encoding = "utf-8"
|
432
|
-
return parse.unquote(string, encoding=encoding)
|
433
|
-
|
434
|
-
|
435
|
-
@cache
|
436
|
-
def compile_jinja2_template(body, extra_curly: bool = False):
|
437
|
-
env: dict = {}
|
438
|
-
if extra_curly:
|
439
|
-
env = {
|
440
|
-
"block_start_string": "{{%",
|
441
|
-
"block_end_string": "%}}",
|
442
|
-
"variable_start_string": "{{{",
|
443
|
-
"variable_end_string": "}}}",
|
444
|
-
"comment_start_string": "{{#",
|
445
|
-
"comment_end_string": "#}}",
|
446
|
-
}
|
447
|
-
|
448
|
-
jinja_env = jinja2.sandbox.SandboxedEnvironment(
|
449
|
-
extensions=[B64EncodeExtension, RaiseErrorExtension],
|
450
|
-
undefined=jinja2.StrictUndefined,
|
451
|
-
**env,
|
452
|
-
)
|
453
|
-
jinja_env.filters.update({
|
454
|
-
"json_to_dict": json_to_dict,
|
455
|
-
"urlescape": urlescape,
|
456
|
-
"urlunescape": urlunescape,
|
457
|
-
"eval": eval_filter,
|
458
|
-
})
|
459
|
-
|
460
|
-
return jinja_env.from_string(body)
|
461
|
-
|
462
|
-
|
463
|
-
def process_jinja2_template(
|
464
|
-
body, vars=None, extra_curly: bool = False, settings=None, secret_reader=None
|
465
|
-
):
|
466
|
-
if vars is None:
|
467
|
-
vars = {}
|
468
|
-
vars.update({
|
469
|
-
"vault": lambda p, k, v=None, allow_not_found=False: lookup_secret(
|
470
|
-
path=p,
|
471
|
-
key=k,
|
472
|
-
version=v,
|
473
|
-
tvars=vars,
|
474
|
-
allow_not_found=allow_not_found,
|
475
|
-
settings=settings,
|
476
|
-
secret_reader=secret_reader,
|
477
|
-
),
|
478
|
-
"github": lambda u, p, r, v=None: lookup_github_file_content(
|
479
|
-
repo=u,
|
480
|
-
path=p,
|
481
|
-
ref=r,
|
482
|
-
tvars=vars,
|
483
|
-
settings=settings,
|
484
|
-
secret_reader=secret_reader,
|
485
|
-
),
|
486
|
-
"urlescape": lambda u, s="/", e=None: urlescape(string=u, safe=s, encoding=e),
|
487
|
-
"urlunescape": lambda u, e=None: urlunescape(string=u, encoding=e),
|
488
|
-
"hash_list": hash_list,
|
489
|
-
"query": lookup_graphql_query_results,
|
490
|
-
"url": url_makes_sense,
|
491
|
-
})
|
492
|
-
try:
|
493
|
-
template = compile_jinja2_template(body, extra_curly)
|
494
|
-
r = template.render(vars)
|
495
|
-
except Exception as e:
|
496
|
-
raise Jinja2TemplateError(e)
|
497
|
-
return r
|
498
|
-
|
499
|
-
|
500
|
-
def process_extracurlyjinja2_template(
|
501
|
-
body, vars=None, env=None, settings=None, secret_reader=None
|
502
|
-
):
|
503
|
-
if vars is None:
|
504
|
-
vars = {}
|
505
|
-
return process_jinja2_template(
|
506
|
-
body,
|
507
|
-
vars=vars,
|
508
|
-
extra_curly=True,
|
509
|
-
settings=settings,
|
510
|
-
secret_reader=secret_reader,
|
511
|
-
)
|
512
|
-
|
513
|
-
|
514
277
|
def check_alertmanager_config(data, path, alertmanager_config_key, decode_base64=False):
|
515
278
|
try:
|
516
279
|
config = data[alertmanager_config_key]
|
@@ -29,6 +29,7 @@ from reconcile.utils import (
|
|
29
29
|
gql,
|
30
30
|
promtool,
|
31
31
|
)
|
32
|
+
from reconcile.utils.jinja2.utils import process_extracurlyjinja2_template
|
32
33
|
from reconcile.utils.runtime.integration import DesiredStateShardConfig
|
33
34
|
from reconcile.utils.semver_helper import make_semver
|
34
35
|
from reconcile.utils.structs import CommandExecutionResult
|
@@ -91,7 +92,7 @@ def fetch_rule_and_tests(
|
|
91
92
|
test_raw_yaml = gql.get_resource(test_path)["content"]
|
92
93
|
|
93
94
|
if rule.resource["type"] == "resource-template-extracurlyjinja2":
|
94
|
-
test_raw_yaml =
|
95
|
+
test_raw_yaml = process_extracurlyjinja2_template(
|
95
96
|
body=test_raw_yaml,
|
96
97
|
vars=variables,
|
97
98
|
settings=vault_settings.dict(by_alias=True),
|
@@ -3,7 +3,7 @@ import json
|
|
3
3
|
import pytest
|
4
4
|
|
5
5
|
from reconcile.ocm_upgrade_scheduler_org_updater import render_policy
|
6
|
-
from reconcile.
|
6
|
+
from reconcile.utils.jinja2.utils import Jinja2TemplateError
|
7
7
|
from reconcile.utils.ocm import (
|
8
8
|
OCMClusterNetwork,
|
9
9
|
OCMSpec,
|
@@ -13,7 +13,6 @@ from reconcile.openshift_base import CurrentStateSpec
|
|
13
13
|
from reconcile.openshift_resources_base import (
|
14
14
|
CheckClusterScopedResourceDuplicates,
|
15
15
|
canonicalize_namespaces,
|
16
|
-
hash_list,
|
17
16
|
ob,
|
18
17
|
)
|
19
18
|
from reconcile.test.fixtures import Fixtures
|
@@ -441,36 +440,6 @@ def test_check_error():
|
|
441
440
|
print(e)
|
442
441
|
|
443
442
|
|
444
|
-
def test_hash_list_empty():
|
445
|
-
assert hash_list([])[:6] == "ca9781"
|
446
|
-
|
447
|
-
|
448
|
-
def test_hash_list_string():
|
449
|
-
assert hash_list(["a", "b"])[:6] == "38760e"
|
450
|
-
assert hash_list(["b", "a"])[:6] == "38760e"
|
451
|
-
|
452
|
-
|
453
|
-
def test_hash_list_int():
|
454
|
-
assert hash_list([1, 2])[:6] == "f37508"
|
455
|
-
assert hash_list([2, 1])[:6] == "f37508"
|
456
|
-
|
457
|
-
|
458
|
-
def test_hash_list_bool():
|
459
|
-
assert hash_list([True, False])[:6] == "e0ca28"
|
460
|
-
assert hash_list([False, True])[:6] == "e0ca28"
|
461
|
-
|
462
|
-
|
463
|
-
def test_hash_list_error():
|
464
|
-
with pytest.raises(RuntimeError):
|
465
|
-
hash_list([{}])
|
466
|
-
|
467
|
-
with pytest.raises(RuntimeError):
|
468
|
-
hash_list([[]])
|
469
|
-
|
470
|
-
with pytest.raises(RuntimeError):
|
471
|
-
hash_list(["a", {}])
|
472
|
-
|
473
|
-
|
474
443
|
def test_cluster_params():
|
475
444
|
with pytest.raises(RuntimeError):
|
476
445
|
orb.run(dry_run=False, exclude_cluster=["test-cluster"])
|
@@ -0,0 +1,123 @@
|
|
1
|
+
import pytest
|
2
|
+
from jsonpath_ng.exceptions import JsonPathParserError
|
3
|
+
|
4
|
+
from reconcile.utils.jinja2.filters import (
|
5
|
+
extract_jsonpath,
|
6
|
+
hash_list,
|
7
|
+
json_pointers,
|
8
|
+
matches_jsonpath,
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
def test_hash_list_empty() -> None:
|
13
|
+
assert hash_list([])[:6] == "ca9781"
|
14
|
+
|
15
|
+
|
16
|
+
def test_hash_list_string() -> None:
|
17
|
+
assert hash_list(["a", "b"])[:6] == "38760e"
|
18
|
+
assert hash_list(["b", "a"])[:6] == "38760e"
|
19
|
+
|
20
|
+
|
21
|
+
def test_hash_list_int() -> None:
|
22
|
+
assert hash_list([1, 2])[:6] == "f37508"
|
23
|
+
assert hash_list([2, 1])[:6] == "f37508"
|
24
|
+
|
25
|
+
|
26
|
+
def test_hash_list_bool() -> None:
|
27
|
+
assert hash_list([True, False])[:6] == "e0ca28"
|
28
|
+
assert hash_list([False, True])[:6] == "e0ca28"
|
29
|
+
|
30
|
+
|
31
|
+
def test_hash_list_error() -> None:
|
32
|
+
with pytest.raises(RuntimeError):
|
33
|
+
hash_list([{}])
|
34
|
+
|
35
|
+
with pytest.raises(RuntimeError):
|
36
|
+
hash_list([[]])
|
37
|
+
|
38
|
+
with pytest.raises(RuntimeError):
|
39
|
+
hash_list(["a", {}])
|
40
|
+
|
41
|
+
|
42
|
+
def test_extract_jsonpath_dict_basic() -> None:
|
43
|
+
input = {"a": "A", "b": {"b1": "B1", "b2": ["B", 2]}}
|
44
|
+
assert extract_jsonpath(input, "a") == ["A"]
|
45
|
+
assert extract_jsonpath(input, "b.b1") == ["B1"]
|
46
|
+
assert extract_jsonpath(input, "b.b2") == [["B", 2]]
|
47
|
+
assert extract_jsonpath(input, "b.b2[0]") == ["B"]
|
48
|
+
assert extract_jsonpath(input, "c") == []
|
49
|
+
|
50
|
+
|
51
|
+
def test_extract_jsonpath_dict_multiple() -> None:
|
52
|
+
input = {
|
53
|
+
"items": [
|
54
|
+
{"name": "a", "value": "A"},
|
55
|
+
{"name": "b", "value": "B1"},
|
56
|
+
{"name": "b", "value": "B2"},
|
57
|
+
]
|
58
|
+
}
|
59
|
+
assert extract_jsonpath(input, "items[0]") == [{"name": "a", "value": "A"}]
|
60
|
+
assert extract_jsonpath(input, "items[?(@.name=='a')]") == [
|
61
|
+
{"name": "a", "value": "A"}
|
62
|
+
]
|
63
|
+
assert extract_jsonpath(input, "items[?(@.name=='a')].value") == ["A"]
|
64
|
+
assert extract_jsonpath(input, "items[?(@.name=='b')]") == [
|
65
|
+
{"name": "b", "value": "B1"},
|
66
|
+
{"name": "b", "value": "B2"},
|
67
|
+
]
|
68
|
+
assert extract_jsonpath(input, "items[?(@.name=='b')].value") == ["B1", "B2"]
|
69
|
+
assert extract_jsonpath(input, "items[?(@.name=='c')].value") == []
|
70
|
+
|
71
|
+
|
72
|
+
def test_extract_jsonpath_list() -> None:
|
73
|
+
input = ["a", "b"]
|
74
|
+
assert extract_jsonpath(input, "[0]") == ["a"]
|
75
|
+
|
76
|
+
|
77
|
+
def test_extract_jsonpath_str() -> None:
|
78
|
+
assert extract_jsonpath("a", "[0]") == ["a"]
|
79
|
+
assert extract_jsonpath("a", "something") == []
|
80
|
+
|
81
|
+
|
82
|
+
def test_extract_jsonpath_none() -> None:
|
83
|
+
assert extract_jsonpath(None, "a") == []
|
84
|
+
|
85
|
+
|
86
|
+
def test_extract_jsonpath_errors() -> None:
|
87
|
+
input = {"a": "A", "b": "B"}
|
88
|
+
with pytest.raises(JsonPathParserError):
|
89
|
+
extract_jsonpath(input, "THIS IS AN INVALID JSONPATH]")
|
90
|
+
with pytest.raises(AssertionError):
|
91
|
+
extract_jsonpath("a", "")
|
92
|
+
|
93
|
+
|
94
|
+
def test_matches_jsonpath() -> None:
|
95
|
+
input = {"a": "A", "b": "B"}
|
96
|
+
assert matches_jsonpath(input, "a")
|
97
|
+
assert not matches_jsonpath(input, "c")
|
98
|
+
with pytest.raises(JsonPathParserError):
|
99
|
+
matches_jsonpath(input, "THIS IS AN INVALID JSONPATH]")
|
100
|
+
with pytest.raises(AssertionError):
|
101
|
+
matches_jsonpath("a", "")
|
102
|
+
with pytest.raises(AssertionError):
|
103
|
+
matches_jsonpath("a", None)
|
104
|
+
|
105
|
+
|
106
|
+
def test_json_pointers() -> None:
|
107
|
+
input = {
|
108
|
+
"items": [
|
109
|
+
{"name": "a", "value": "A"},
|
110
|
+
{"name": "b", "value": "B1"},
|
111
|
+
{"name": "b", "value": "B2"},
|
112
|
+
]
|
113
|
+
}
|
114
|
+
assert json_pointers(input, "items") == ["/items"]
|
115
|
+
assert json_pointers(input, "items[*]") == ["/items/0", "/items/1", "/items/2"]
|
116
|
+
assert json_pointers(input, "items[0]") == ["/items/0"]
|
117
|
+
assert json_pointers(input, "items[0].name") == ["/items/0/name"]
|
118
|
+
assert json_pointers(input, "items[4]") == []
|
119
|
+
assert json_pointers(input, "items[4].name") == []
|
120
|
+
|
121
|
+
assert json_pointers(input, "items[?@.name=='a']") == ["/items/0"]
|
122
|
+
assert json_pointers(input, "items[?@.name=='b']") == ["/items/1", "/items/2"]
|
123
|
+
assert json_pointers(input, "items[?@.name=='c']") == []
|
@@ -15,7 +15,7 @@ import yaml
|
|
15
15
|
from pydantic import BaseModel
|
16
16
|
from pydantic.dataclasses import dataclass
|
17
17
|
|
18
|
-
from reconcile import
|
18
|
+
from reconcile.utils.jinja2.utils import process_jinja2_template
|
19
19
|
from reconcile.utils.metrics import GaugeMetric
|
20
20
|
from reconcile.utils.openshift_resource import (
|
21
21
|
SECRET_MAX_KEY_LENGTH,
|
@@ -58,9 +58,7 @@ class GenericSecretOutputFormatConfig(OutputFormatProcessor):
|
|
58
58
|
if self.data:
|
59
59
|
# the jinja2 rendering has the capabilitiy to change the passed
|
60
60
|
# vars dict - make a copy to protect against it
|
61
|
-
rendered_data =
|
62
|
-
self.data, dict(vars)
|
63
|
-
)
|
61
|
+
rendered_data = process_jinja2_template(self.data, dict(vars))
|
64
62
|
parsed_data = yaml.safe_load(rendered_data)
|
65
63
|
self.validate_k8s_secret_data(parsed_data)
|
66
64
|
return cast(dict[str, str], parsed_data)
|
File without changes
|
@@ -1,15 +1,17 @@
|
|
1
1
|
import base64
|
2
2
|
import textwrap
|
3
|
+
from typing import Callable
|
3
4
|
|
4
5
|
from jinja2 import nodes
|
5
6
|
from jinja2.exceptions import TemplateRuntimeError
|
6
7
|
from jinja2.ext import Extension
|
8
|
+
from jinja2.parser import Parser
|
7
9
|
|
8
10
|
|
9
11
|
class B64EncodeExtension(Extension):
|
10
12
|
tags = {"b64encode"}
|
11
13
|
|
12
|
-
def parse(self, parser):
|
14
|
+
def parse(self, parser: Parser) -> nodes.CallBlock:
|
13
15
|
lineno = next(parser.stream).lineno
|
14
16
|
|
15
17
|
body = parser.parse_statements(["name:endb64encode"], drop_needle=True)
|
@@ -19,7 +21,7 @@ class B64EncodeExtension(Extension):
|
|
19
21
|
).set_lineno(lineno)
|
20
22
|
|
21
23
|
@staticmethod
|
22
|
-
def _b64encode(caller):
|
24
|
+
def _b64encode(caller: Callable) -> str:
|
23
25
|
content = caller()
|
24
26
|
content = textwrap.dedent(content)
|
25
27
|
return base64.b64encode(content.encode()).decode("utf-8")
|
@@ -28,7 +30,7 @@ class B64EncodeExtension(Extension):
|
|
28
30
|
class RaiseErrorExtension(Extension):
|
29
31
|
tags = {"raise_error"}
|
30
32
|
|
31
|
-
def parse(self, parser):
|
33
|
+
def parse(self, parser: Parser) -> nodes.CallBlock:
|
32
34
|
lineno = next(parser.stream).lineno
|
33
35
|
|
34
36
|
msg = parser.parse_expression()
|
@@ -42,5 +44,5 @@ class RaiseErrorExtension(Extension):
|
|
42
44
|
)
|
43
45
|
|
44
46
|
@staticmethod
|
45
|
-
def _raise_error(msg, caller):
|
47
|
+
def _raise_error(msg: str, caller: Callable) -> None:
|
46
48
|
raise TemplateRuntimeError(msg)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
import hashlib
|
2
|
+
import json
|
3
|
+
import re
|
4
|
+
from collections.abc import Iterable
|
5
|
+
from typing import Any, Optional
|
6
|
+
from urllib import parse
|
7
|
+
|
8
|
+
import jinja2
|
9
|
+
|
10
|
+
from reconcile.utils.jsonpath import parse_jsonpath
|
11
|
+
|
12
|
+
|
13
|
+
def json_to_dict(input: str) -> Any:
|
14
|
+
"""Jinja2 filter to parse JSON strings into dictionaries.
|
15
|
+
This becomes useful to access Graphql queries data (labels)
|
16
|
+
:param input: json string
|
17
|
+
:return: dict with the parsed inputs contents
|
18
|
+
"""
|
19
|
+
data = json.loads(input)
|
20
|
+
return data
|
21
|
+
|
22
|
+
|
23
|
+
def urlescape(string: str, safe: str = "/", encoding: Optional[str] = None) -> str:
|
24
|
+
"""Jinja2 filter that is a simple wrapper around urllib's URL quoting
|
25
|
+
functions that takes a string value and makes it safe for use as URL
|
26
|
+
components escaping any reserved characters using URL encoding. See:
|
27
|
+
urllib.parse.quote() and urllib.parse.quote_plus() for reference.
|
28
|
+
|
29
|
+
:param str string: String value to escape.
|
30
|
+
:param str safe: Optional characters that should not be escaped.
|
31
|
+
:param encoding: Encoding to apply to the string to be escaped. Defaults
|
32
|
+
to UTF-8. Unsupported characters raise a UnicodeEncodeError error.
|
33
|
+
:type encoding: typing.Optional[str]
|
34
|
+
:returns: A string with reserved characters escaped.
|
35
|
+
:rtype: str
|
36
|
+
"""
|
37
|
+
return parse.quote(string, safe=safe, encoding=encoding)
|
38
|
+
|
39
|
+
|
40
|
+
def urlunescape(string: str, encoding: Optional[str] = None) -> str:
|
41
|
+
"""Jinja2 filter that is a simple wrapper around urllib's URL unquoting
|
42
|
+
functions that takes an URL-encoded string value and unescapes it
|
43
|
+
replacing any URL-encoded values with their character equivalent. See:
|
44
|
+
urllib.parse.unquote() and urllib.parse.unquote_plus() for reference.
|
45
|
+
|
46
|
+
:param str string: String value to unescape.
|
47
|
+
:param encoding: Encoding to apply to the string to be unescaped. Defaults
|
48
|
+
to UTF-8. Unsupported characters are replaced by placeholder values.
|
49
|
+
:type encoding: typing.Optional[str]
|
50
|
+
:returns: A string with URL-encoded sequences unescaped.
|
51
|
+
:rtype: str
|
52
|
+
"""
|
53
|
+
if encoding is None:
|
54
|
+
encoding = "utf-8"
|
55
|
+
return parse.unquote(string, encoding=encoding)
|
56
|
+
|
57
|
+
|
58
|
+
def eval_filter(input: str, **kwargs: dict[str, Any]) -> str:
|
59
|
+
"""Jinja2 filter be used when the string
|
60
|
+
is in itself a jinja2 template that must be
|
61
|
+
evaluated with kwargs. For example in the case
|
62
|
+
of the slo-document expression fields.
|
63
|
+
:param input: template string
|
64
|
+
:kwargs: variables that will be used to evaluate the
|
65
|
+
input string
|
66
|
+
:return: rendered string
|
67
|
+
"""
|
68
|
+
return jinja2.Template(input).render(**kwargs)
|
69
|
+
|
70
|
+
|
71
|
+
def hash_list(input: Iterable) -> str:
|
72
|
+
"""
|
73
|
+
Deterministic hash of a list for jinja2 templates.
|
74
|
+
The order of the list doesn't matter as it is sorted
|
75
|
+
before hashing. Note, that the list elements
|
76
|
+
must be flat primitives (no dicts/lists).
|
77
|
+
"""
|
78
|
+
lst = list(input)
|
79
|
+
str_lst = []
|
80
|
+
for el in lst:
|
81
|
+
if isinstance(el, (list, dict)):
|
82
|
+
raise RuntimeError(
|
83
|
+
f"jinja2 hash_list function received non-primitive value {el}. All values received {lst}"
|
84
|
+
)
|
85
|
+
str_lst.append(str(el))
|
86
|
+
msg = "a" # keep non-empty for hashing empty list
|
87
|
+
msg += "".join(sorted(str_lst))
|
88
|
+
m = hashlib.sha256()
|
89
|
+
m.update(msg.encode("utf-8"))
|
90
|
+
return m.hexdigest()
|
91
|
+
|
92
|
+
|
93
|
+
def _find_jsonpath(input: Any, jsonpath: str | None) -> Any:
|
94
|
+
assert jsonpath is not None and len(jsonpath) > 0
|
95
|
+
return parse_jsonpath(jsonpath).find(input)
|
96
|
+
|
97
|
+
|
98
|
+
def extract_jsonpath(input: Any, jsonpath: str) -> Any:
|
99
|
+
"""
|
100
|
+
Extracts data from the input using jsonpath.
|
101
|
+
The result is a list of matching elements.
|
102
|
+
The list will be empty if nothing matches.
|
103
|
+
"""
|
104
|
+
return [i.value for i in _find_jsonpath(input, jsonpath)]
|
105
|
+
|
106
|
+
|
107
|
+
def matches_jsonpath(input: Any, jsonpath: str | None) -> bool:
|
108
|
+
"""
|
109
|
+
Returns True if the input matches the provided jsonpath
|
110
|
+
"""
|
111
|
+
return len(_find_jsonpath(input, jsonpath)) > 0
|
112
|
+
|
113
|
+
|
114
|
+
def _convert_pointer(pointer: str) -> str:
|
115
|
+
"""
|
116
|
+
Converts a jsonpath_ng pointer (eg "items.[2].type.[3]")
|
117
|
+
to a rfc6901 one (https://www.rfc-editor.org/rfc/rfc6901)
|
118
|
+
"/items/2/type/3"
|
119
|
+
"""
|
120
|
+
elems = [e[1:-1] if re.match(r"\[\d\]", e) else e for e in pointer.split(".")]
|
121
|
+
return "/" + "/".join(elems)
|
122
|
+
|
123
|
+
|
124
|
+
def json_pointers(input: Any, jsonpath: str) -> list[str]:
|
125
|
+
"""
|
126
|
+
Finds the RFC6901 JSON pointers of the input elements matching the given jsonpath
|
127
|
+
"""
|
128
|
+
return [_convert_pointer(str(i.full_path)) for i in _find_jsonpath(input, jsonpath)]
|
@@ -0,0 +1,188 @@
|
|
1
|
+
from functools import cache
|
2
|
+
from typing import Any, Optional
|
3
|
+
|
4
|
+
import jinja2
|
5
|
+
from jinja2.sandbox import SandboxedEnvironment
|
6
|
+
from sretoolbox.utils import retry
|
7
|
+
|
8
|
+
from reconcile.checkpoint import url_makes_sense
|
9
|
+
from reconcile.github_users import init_github
|
10
|
+
from reconcile.utils import gql
|
11
|
+
from reconcile.utils.jinja2.extensions import B64EncodeExtension, RaiseErrorExtension
|
12
|
+
from reconcile.utils.jinja2.filters import (
|
13
|
+
eval_filter,
|
14
|
+
extract_jsonpath,
|
15
|
+
hash_list,
|
16
|
+
json_pointers,
|
17
|
+
json_to_dict,
|
18
|
+
matches_jsonpath,
|
19
|
+
urlescape,
|
20
|
+
urlunescape,
|
21
|
+
)
|
22
|
+
from reconcile.utils.secret_reader import SecretNotFound, SecretReader, SecretReaderBase
|
23
|
+
|
24
|
+
|
25
|
+
class Jinja2TemplateError(Exception):
|
26
|
+
def __init__(self, msg: Any):
|
27
|
+
super().__init__("error processing jinja2 template: " + str(msg))
|
28
|
+
|
29
|
+
|
30
|
+
@cache
|
31
|
+
def compile_jinja2_template(body: str, extra_curly: bool = False) -> Any:
|
32
|
+
env: dict = {}
|
33
|
+
if extra_curly:
|
34
|
+
env = {
|
35
|
+
"block_start_string": "{{%",
|
36
|
+
"block_end_string": "%}}",
|
37
|
+
"variable_start_string": "{{{",
|
38
|
+
"variable_end_string": "}}}",
|
39
|
+
"comment_start_string": "{{#",
|
40
|
+
"comment_end_string": "#}}",
|
41
|
+
}
|
42
|
+
|
43
|
+
jinja_env = SandboxedEnvironment(
|
44
|
+
extensions=[B64EncodeExtension, RaiseErrorExtension],
|
45
|
+
undefined=jinja2.StrictUndefined,
|
46
|
+
**env,
|
47
|
+
)
|
48
|
+
jinja_env.filters.update({
|
49
|
+
"json_to_dict": json_to_dict,
|
50
|
+
"urlescape": urlescape,
|
51
|
+
"urlunescape": urlunescape,
|
52
|
+
"eval": eval_filter,
|
53
|
+
"extract_jsonpath": extract_jsonpath,
|
54
|
+
"matches_jsonpath": matches_jsonpath,
|
55
|
+
"json_pointers": json_pointers,
|
56
|
+
})
|
57
|
+
|
58
|
+
return jinja_env.from_string(body)
|
59
|
+
|
60
|
+
|
61
|
+
def lookup_github_file_content(
|
62
|
+
repo: str,
|
63
|
+
path: str,
|
64
|
+
ref: str,
|
65
|
+
tvars: Optional[dict[str, Any]] = None,
|
66
|
+
settings: Optional[dict[str, Any]] = None,
|
67
|
+
secret_reader: Optional[SecretReaderBase] = None,
|
68
|
+
) -> str:
|
69
|
+
if tvars is not None:
|
70
|
+
repo = process_jinja2_template(
|
71
|
+
body=repo, vars=tvars, settings=settings, secret_reader=secret_reader
|
72
|
+
)
|
73
|
+
path = process_jinja2_template(
|
74
|
+
body=path, vars=tvars, settings=settings, secret_reader=secret_reader
|
75
|
+
)
|
76
|
+
ref = process_jinja2_template(
|
77
|
+
body=ref, vars=tvars, settings=settings, secret_reader=secret_reader
|
78
|
+
)
|
79
|
+
|
80
|
+
gh = init_github()
|
81
|
+
c = gh.get_repo(repo).get_contents(path, ref).decoded_content
|
82
|
+
return c.decode("utf-8")
|
83
|
+
|
84
|
+
|
85
|
+
def lookup_graphql_query_results(query: str, **kwargs: dict[str, Any]) -> list[Any]:
|
86
|
+
gqlapi = gql.get_api()
|
87
|
+
resource = gqlapi.get_resource(query)["content"]
|
88
|
+
rendered_resource = jinja2.Template(resource).render(**kwargs)
|
89
|
+
results = list(gqlapi.query(rendered_resource).values())[0]
|
90
|
+
return results
|
91
|
+
|
92
|
+
|
93
|
+
@retry()
|
94
|
+
def lookup_secret(
|
95
|
+
path: str,
|
96
|
+
key: str,
|
97
|
+
version: Optional[str] = None,
|
98
|
+
tvars: Optional[dict[str, Any]] = None,
|
99
|
+
allow_not_found: bool = False,
|
100
|
+
settings: Optional[dict[str, Any]] = None,
|
101
|
+
secret_reader: Optional[SecretReaderBase] = None,
|
102
|
+
) -> Optional[str]:
|
103
|
+
if tvars is not None:
|
104
|
+
path = process_jinja2_template(
|
105
|
+
body=path, vars=tvars, settings=settings, secret_reader=secret_reader
|
106
|
+
)
|
107
|
+
key = process_jinja2_template(
|
108
|
+
body=key, vars=tvars, settings=settings, secret_reader=secret_reader
|
109
|
+
)
|
110
|
+
if version and not isinstance(version, int):
|
111
|
+
version = process_jinja2_template(
|
112
|
+
body=version, vars=tvars, settings=settings, secret_reader=secret_reader
|
113
|
+
)
|
114
|
+
secret = {"path": path, "field": key, "version": version}
|
115
|
+
try:
|
116
|
+
if not secret_reader:
|
117
|
+
secret_reader = SecretReader(settings)
|
118
|
+
return secret_reader.read(secret)
|
119
|
+
except SecretNotFound as e:
|
120
|
+
if allow_not_found:
|
121
|
+
return None
|
122
|
+
raise FetchSecretError(e)
|
123
|
+
except Exception as e:
|
124
|
+
raise FetchSecretError(e)
|
125
|
+
|
126
|
+
|
127
|
+
def process_jinja2_template(
|
128
|
+
body: str,
|
129
|
+
vars: Optional[dict[str, Any]] = None,
|
130
|
+
extra_curly: bool = False,
|
131
|
+
settings: Optional[dict[str, Any]] = None,
|
132
|
+
secret_reader: Optional[SecretReaderBase] = None,
|
133
|
+
) -> Any:
|
134
|
+
if vars is None:
|
135
|
+
vars = {}
|
136
|
+
vars.update({
|
137
|
+
"vault": lambda p, k, v=None: lookup_secret(
|
138
|
+
path=p,
|
139
|
+
key=k,
|
140
|
+
version=v,
|
141
|
+
tvars=vars,
|
142
|
+
allow_not_found=False,
|
143
|
+
settings=settings,
|
144
|
+
secret_reader=secret_reader,
|
145
|
+
),
|
146
|
+
"github": lambda u, p, r, v=None: lookup_github_file_content(
|
147
|
+
repo=u,
|
148
|
+
path=p,
|
149
|
+
ref=r,
|
150
|
+
tvars=vars,
|
151
|
+
settings=settings,
|
152
|
+
secret_reader=secret_reader,
|
153
|
+
),
|
154
|
+
"urlescape": lambda u, s="/", e=None: urlescape(string=u, safe=s, encoding=e),
|
155
|
+
"urlunescape": lambda u, e=None: urlunescape(string=u, encoding=e),
|
156
|
+
"hash_list": hash_list,
|
157
|
+
"query": lookup_graphql_query_results,
|
158
|
+
"url": url_makes_sense,
|
159
|
+
})
|
160
|
+
try:
|
161
|
+
template = compile_jinja2_template(body, extra_curly)
|
162
|
+
r = template.render(vars)
|
163
|
+
except Exception as e:
|
164
|
+
raise Jinja2TemplateError(e)
|
165
|
+
return r
|
166
|
+
|
167
|
+
|
168
|
+
def process_extracurlyjinja2_template(
|
169
|
+
body: str,
|
170
|
+
vars: Optional[dict[str, Any]] = None,
|
171
|
+
extra_curly: bool = True,
|
172
|
+
settings: Optional[dict[str, Any]] = None,
|
173
|
+
secret_reader: Optional[SecretReaderBase] = None,
|
174
|
+
) -> Any:
|
175
|
+
if vars is None:
|
176
|
+
vars = {}
|
177
|
+
return process_jinja2_template(
|
178
|
+
body,
|
179
|
+
vars=vars,
|
180
|
+
extra_curly=True,
|
181
|
+
settings=settings,
|
182
|
+
secret_reader=secret_reader,
|
183
|
+
)
|
184
|
+
|
185
|
+
|
186
|
+
class FetchSecretError(Exception):
|
187
|
+
def __init__(self, msg: Any):
|
188
|
+
super().__init__("error fetching secret: " + str(msg))
|
@@ -2031,7 +2031,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
2031
2031
|
@staticmethod
|
2032
2032
|
def resolve_templated_parameters(saas_files: Iterable[SaasFile]) -> None:
|
2033
2033
|
"""Resolve templated target parameters in saas files."""
|
2034
|
-
from reconcile.
|
2034
|
+
from reconcile.utils.jinja2.utils import ( # noqa: PLC0415 - # avoid circular import
|
2035
2035
|
compile_jinja2_template,
|
2036
2036
|
)
|
2037
2037
|
|
@@ -140,7 +140,6 @@ from terrascript.resource import (
|
|
140
140
|
random_id,
|
141
141
|
)
|
142
142
|
|
143
|
-
import reconcile.openshift_resources_base as orb
|
144
143
|
import reconcile.utils.aws_helper as awsh
|
145
144
|
from reconcile import queries
|
146
145
|
from reconcile.github_org import get_default_config
|
@@ -176,6 +175,7 @@ from reconcile.utils.external_resources import (
|
|
176
175
|
from reconcile.utils.git import is_file_in_git_repo
|
177
176
|
from reconcile.utils.gitlab_api import GitLabApi
|
178
177
|
from reconcile.utils.jenkins_api import JenkinsApi
|
178
|
+
from reconcile.utils.jinja2.utils import process_extracurlyjinja2_template
|
179
179
|
from reconcile.utils.ocm import OCMMap
|
180
180
|
from reconcile.utils.password_validator import (
|
181
181
|
PasswordPolicy,
|
@@ -5342,7 +5342,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
|
|
5342
5342
|
part = []
|
5343
5343
|
for c in cloudinit_configs:
|
5344
5344
|
raw = self.get_raw_values(c["content"])
|
5345
|
-
content =
|
5345
|
+
content = process_extracurlyjinja2_template(
|
5346
5346
|
body=raw["content"], vars=vars, secret_reader=self.secret_reader
|
5347
5347
|
)
|
5348
5348
|
# https://www.terraform.io/docs/language/expressions/strings.html#escape-sequences
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc585.dist-info → qontract_reconcile-0.10.1rc586.dist-info}/top_level.txt
RENAMED
File without changes
|