qontract-reconcile 0.10.1rc607__py3-none-any.whl → 0.10.1rc609__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.1rc607
3
+ Version: 0.10.1rc609
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
@@ -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=WqujVeoWJhYWxV-RmpIf4XYfR8JuyF4wHf1B2xOywcE,91518
12
+ reconcile/cli.py,sha256=iYjF1R-M2FdokkMzjlCFennx-E9Go8xdJN5QGTFZMjU,91560
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
@@ -225,6 +225,7 @@ reconcile/gql_definitions/common/saas_files.py,sha256=B7HE_jV5ky8AFxOCleSg2RYDrt
225
225
  reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=gcTU9jrsNq9-HX-oOkj-nEZKYFTRytDHLs4SpEs93aw,2755
226
226
  reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
227
227
  reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
228
+ reconcile/gql_definitions/common/state_aws_account.py,sha256=LAdpCG2-ykVpWBPO0Zu1WvG-hwKXyDC0fJQxJRpbqCk,2198
228
229
  reconcile/gql_definitions/common/users.py,sha256=uDiEDqa4QP89I2oFuKhCtVB61ZviIt7Y75fgrcCm7M4,1681
229
230
  reconcile/gql_definitions/dashdotdb_slo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
231
  reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py,sha256=zUa-CmpOwiymVmOV6KwDHH5mMl06p000320FcOas6hU,4315
@@ -518,6 +519,7 @@ reconcile/typed_queries/aws_vpcs.py,sha256=bM7dWpVHV6J1WYjgHOe2rfAl4vKpUoC8khPQS
518
519
  reconcile/typed_queries/clusters.py,sha256=tptLP2Ov1lNqtDGuPKBOVvpDtREm5CQFDDVyp0xKkRQ,506
519
520
  reconcile/typed_queries/clusters_minimal.py,sha256=flvs4oXVR4b971h-zMTK3C2GL3bjMnvZgrAeI3XgBQQ,517
520
521
  reconcile/typed_queries/clusters_with_peering.py,sha256=lIai7SJJD0bqIJbe7virgrbYRqjLouSL2OpJD0itpAY,330
522
+ reconcile/typed_queries/get_state_aws_account.py,sha256=CSJjVPWsUZ2rkGIt8ehoQt7hokFqrUDgG9HFlg2lVD8,492
521
523
  reconcile/typed_queries/github_orgs.py,sha256=UZhoPl8qvA_tcO7CZlN8GuMKckt3ywd47Suu61rgHsc,258
522
524
  reconcile/typed_queries/gitlab_instances.py,sha256=ZVQHy2W9xIp53f5qYkjKLHLHgOVtQpxTfcmM1C2046g,291
523
525
  reconcile/typed_queries/jira_settings.py,sha256=i0ddx5xxHrM1v-9mtL_6OB-jBFLw7-HS6xenpIDjrkw,570
@@ -541,7 +543,7 @@ reconcile/utils/aggregated_list.py,sha256=pkYoBj7WwmaNgEefETqEOFTnQMcUzHE3mdsVdz
541
543
  reconcile/utils/amtool.py,sha256=JV5-to_e_FaIcvJWTKYA9d6L3LwzwijM0MjUWn83eD4,2204
542
544
  reconcile/utils/aws_api.py,sha256=sgmxCXYle0jjmMHPpBOAKl-kWTDWwuc0AlLO2NmDB8M,65612
543
545
  reconcile/utils/aws_helper.py,sha256=6Nfgsz0aQ97LBAJ0JBRdnPaFTAkEBSqXvCH6_pVIWdw,2006
544
- reconcile/utils/binary.py,sha256=3IBnwjKakHM367skPPvG6yVSQYjKt5muQlFNdoa63DU,2352
546
+ reconcile/utils/binary.py,sha256=EsOGg82Y2QJh91SGJE0tYpBKqU0iaaagQVoYONBtQn8,2359
545
547
  reconcile/utils/config.py,sha256=aId5zrPjM_84u_T4yTRE_Psu3zo5-5_JCR6_7Wgv5UQ,990
546
548
  reconcile/utils/constants.py,sha256=pOUd97bqZdsAu5RWJ8NUs9cwCY7K9y0eW9VVeJ4fZIU,138
547
549
  reconcile/utils/data_structures.py,sha256=VyKfnlNJTiRvZKNpfgIrjESQ2YgmEpWuPQXT14WA1vI,311
@@ -603,7 +605,7 @@ reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,8
603
605
  reconcile/utils/slack_api.py,sha256=OPmzU6L9rJx2XXDlZkMlxLjOWu17yC-fVCoUItzQrXw,16295
604
606
  reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
605
607
  reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4dA,2287
606
- reconcile/utils/state.py,sha256=SAa6QLHu9lr0yqLCBy2AypNx1IPCJWlrRBrvlzAKsOU,14505
608
+ reconcile/utils/state.py,sha256=zjsprjbOb0WddzmAvh8ACqAt0fcayrX2YPfz7qceRWw,16090
607
609
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
608
610
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
609
611
  reconcile/utils/terraform_client.py,sha256=6hcC0ibXkiFBn-0krVl11Bz29QcW2RI7l40pmTRrRfg,32092
@@ -710,8 +712,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
710
712
  tools/test/test_qontract_cli.py,sha256=OvalpVRfY4pNmpMaWHHYqBjV68b1eGQjX8SCyTAXb1w,3501
711
713
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
712
714
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
713
- qontract_reconcile-0.10.1rc607.dist-info/METADATA,sha256=YA_uMko_z5JCYKe6FRbUNu7lVLlorRq_4HKpwJDrj3Q,2382
714
- qontract_reconcile-0.10.1rc607.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
715
- qontract_reconcile-0.10.1rc607.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
716
- qontract_reconcile-0.10.1rc607.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
717
- qontract_reconcile-0.10.1rc607.dist-info/RECORD,,
715
+ qontract_reconcile-0.10.1rc609.dist-info/METADATA,sha256=oL9FPRy78cqHHZ7uyBOLtfr24QUHtZ_mv0Vc-ElwUOg,2382
716
+ qontract_reconcile-0.10.1rc609.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
717
+ qontract_reconcile-0.10.1rc609.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
718
+ qontract_reconcile-0.10.1rc609.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
719
+ qontract_reconcile-0.10.1rc609.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -43,10 +43,10 @@ from reconcile.utils.runtime.runner import (
43
43
  )
44
44
  from reconcile.utils.unleash import get_feature_toggle_state
45
45
 
46
- TERRAFORM_VERSION = "0.13.7"
46
+ TERRAFORM_VERSION = ["0.13.7"]
47
47
  TERRAFORM_VERSION_REGEX = r"^Terraform\sv([\d]+\.[\d]+\.[\d]+)$"
48
48
 
49
- OC_VERSION = "4.12.46"
49
+ OC_VERSIONS = ["4.12.46", "4.10.15"]
50
50
  OC_VERSION_REGEX = r"^Client\sVersion:\s([\d]+\.[\d]+\.[\d]+)$"
51
51
 
52
52
 
@@ -793,7 +793,7 @@ def github_validator(ctx):
793
793
  @integration.command(short_help="Configures ClusterRolebindings in OpenShift clusters.")
794
794
  @threaded()
795
795
  @binary(["oc", "ssh"])
796
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
796
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
797
797
  @internal()
798
798
  @use_jump_host()
799
799
  @click.pass_context
@@ -812,7 +812,7 @@ def openshift_clusterrolebindings(ctx, thread_pool_size, internal, use_jump_host
812
812
  @integration.command(short_help="Configures Rolebindings in OpenShift clusters.")
813
813
  @threaded()
814
814
  @binary(["oc", "ssh"])
815
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
815
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
816
816
  @internal()
817
817
  @use_jump_host()
818
818
  @click.pass_context
@@ -831,7 +831,7 @@ def openshift_rolebindings(ctx, thread_pool_size, internal, use_jump_host):
831
831
  @integration.command(short_help="Manages OpenShift Groups.")
832
832
  @threaded()
833
833
  @binary(["oc", "ssh"])
834
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
834
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
835
835
  @internal()
836
836
  @use_jump_host()
837
837
  @click.pass_context
@@ -846,7 +846,7 @@ def openshift_groups(ctx, thread_pool_size, internal, use_jump_host):
846
846
  @integration.command(short_help="Deletion of users from OpenShift clusters.")
847
847
  @threaded()
848
848
  @binary(["oc", "ssh"])
849
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
849
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
850
850
  @internal()
851
851
  @use_jump_host()
852
852
  @click.pass_context
@@ -863,7 +863,7 @@ def openshift_users(ctx, thread_pool_size, internal, use_jump_host):
863
863
  )
864
864
  @threaded()
865
865
  @binary(["oc", "ssh"])
866
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
866
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
867
867
  @internal()
868
868
  @use_jump_host()
869
869
  @vault_output_path
@@ -1032,7 +1032,7 @@ def jira_watcher(ctx):
1032
1032
  short_help="Watches for OpenShift upgrades and sends notifications."
1033
1033
  )
1034
1034
  @binary(["oc", "ssh"])
1035
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1035
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1036
1036
  @threaded()
1037
1037
  @internal()
1038
1038
  @use_jump_host()
@@ -1181,7 +1181,7 @@ def aws_support_cases_sos(ctx, gitlab_project_id, thread_pool_size):
1181
1181
  @integration.command(short_help="Manages OpenShift Resources.")
1182
1182
  @threaded()
1183
1183
  @binary(["oc", "ssh", "amtool"])
1184
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1184
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1185
1185
  @internal()
1186
1186
  @use_jump_host()
1187
1187
  @cluster_name
@@ -1216,7 +1216,7 @@ def openshift_resources(
1216
1216
  @throughput
1217
1217
  @use_jump_host()
1218
1218
  @binary(["oc", "ssh"])
1219
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1219
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1220
1220
  @click.option("--saas-file-name", default=None, help="saas-file to act on.")
1221
1221
  @click.option("--env-name", default=None, help="environment to deploy to.")
1222
1222
  @trigger_integration
@@ -1258,7 +1258,7 @@ def openshift_saas_deploy(
1258
1258
  help="bundle sha to compare to to find changes",
1259
1259
  )
1260
1260
  @binary(["oc", "ssh"])
1261
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1261
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1262
1262
  @use_jump_host()
1263
1263
  @click.pass_context
1264
1264
  def openshift_saas_deploy_change_tester(
@@ -1293,7 +1293,7 @@ def saas_file_validator(ctx):
1293
1293
  @integration.command(short_help="Trigger deployments when a commit changed for a ref.")
1294
1294
  @threaded()
1295
1295
  @binary(["oc", "ssh"])
1296
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1296
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1297
1297
  @internal()
1298
1298
  @use_jump_host()
1299
1299
  @include_trigger_trace
@@ -1316,7 +1316,7 @@ def openshift_saas_deploy_trigger_moving_commits(
1316
1316
  @integration.command(short_help="Trigger deployments when upstream job runs.")
1317
1317
  @threaded()
1318
1318
  @binary(["oc", "ssh"])
1319
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1319
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1320
1320
  @internal()
1321
1321
  @use_jump_host()
1322
1322
  @include_trigger_trace
@@ -1339,7 +1339,7 @@ def openshift_saas_deploy_trigger_upstream_jobs(
1339
1339
  @integration.command(short_help="Trigger deployments when images are pushed.")
1340
1340
  @threaded()
1341
1341
  @binary(["oc", "ssh"])
1342
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1342
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1343
1343
  @internal()
1344
1344
  @use_jump_host()
1345
1345
  @include_trigger_trace
@@ -1362,7 +1362,7 @@ def openshift_saas_deploy_trigger_images(
1362
1362
  @integration.command(short_help="Trigger deployments when configuration changes.")
1363
1363
  @threaded()
1364
1364
  @binary(["oc", "ssh"])
1365
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1365
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1366
1366
  @internal()
1367
1367
  @use_jump_host()
1368
1368
  @include_trigger_trace
@@ -1385,7 +1385,7 @@ def openshift_saas_deploy_trigger_configs(
1385
1385
  @integration.command(short_help="Clean up deployment related resources.")
1386
1386
  @threaded()
1387
1387
  @binary(["oc", "ssh"])
1388
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1388
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1389
1389
  @internal()
1390
1390
  @use_jump_host()
1391
1391
  @click.pass_context
@@ -1444,7 +1444,7 @@ def gitlab_labeler(ctx, gitlab_project_id, gitlab_merge_request_id):
1444
1444
  @integration.command(short_help="Manages labels on OpenShift namespaces.")
1445
1445
  @threaded()
1446
1446
  @binary(["oc", "ssh"])
1447
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1447
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1448
1448
  @internal()
1449
1449
  @use_jump_host()
1450
1450
  @click.pass_context
@@ -1463,7 +1463,7 @@ def openshift_namespace_labels(ctx, thread_pool_size, internal, use_jump_host):
1463
1463
  @integration.command(short_help="Manages OpenShift Namespaces.")
1464
1464
  @threaded()
1465
1465
  @binary(["oc", "ssh"])
1466
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1466
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1467
1467
  @internal()
1468
1468
  @use_jump_host()
1469
1469
  @cluster_name
@@ -1488,7 +1488,7 @@ def openshift_namespaces(
1488
1488
  @integration.command(short_help="Manages OpenShift NetworkPolicies.")
1489
1489
  @threaded()
1490
1490
  @binary(["oc", "ssh"])
1491
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1491
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1492
1492
  @internal()
1493
1493
  @use_jump_host()
1494
1494
  @click.pass_context
@@ -1508,7 +1508,7 @@ def openshift_network_policies(ctx, thread_pool_size, internal, use_jump_host):
1508
1508
  @threaded()
1509
1509
  @take_over()
1510
1510
  @binary(["oc", "ssh"])
1511
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1511
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1512
1512
  @internal()
1513
1513
  @use_jump_host()
1514
1514
  @click.pass_context
@@ -1529,7 +1529,7 @@ def openshift_limitranges(ctx, thread_pool_size, internal, use_jump_host, take_o
1529
1529
  @threaded()
1530
1530
  @take_over()
1531
1531
  @binary(["oc", "ssh"])
1532
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1532
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1533
1533
  @internal()
1534
1534
  @use_jump_host()
1535
1535
  @click.pass_context
@@ -1549,7 +1549,7 @@ def openshift_resourcequotas(ctx, thread_pool_size, internal, use_jump_host, tak
1549
1549
  @integration.command(short_help="Manages OpenShift Secrets from Vault.")
1550
1550
  @threaded()
1551
1551
  @binary(["oc", "ssh"])
1552
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1552
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1553
1553
  @internal()
1554
1554
  @use_jump_host()
1555
1555
  @cluster_name
@@ -1574,7 +1574,7 @@ def openshift_vault_secrets(
1574
1574
  @integration.command(short_help="Manages OpenShift Routes.")
1575
1575
  @threaded()
1576
1576
  @binary(["oc", "ssh"])
1577
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1577
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1578
1578
  @internal()
1579
1579
  @use_jump_host()
1580
1580
  @cluster_name
@@ -1899,7 +1899,7 @@ def template_renderer(ctx, app_interface_data_path):
1899
1899
  @threaded()
1900
1900
  @binary(["terraform", "oc", "git"])
1901
1901
  @binary_version("terraform", ["version"], TERRAFORM_VERSION_REGEX, TERRAFORM_VERSION)
1902
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
1902
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1903
1903
  @internal()
1904
1904
  @use_jump_host()
1905
1905
  @enable_deletion(default=False)
@@ -2922,7 +2922,7 @@ def resource_scraper(ctx, namespace_name, resource_kind, vault_output_path):
2922
2922
  @integration.command(short_help="Manages user access for GABI instances.")
2923
2923
  @threaded()
2924
2924
  @binary(["oc", "ssh"])
2925
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
2925
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
2926
2926
  @internal()
2927
2927
  @use_jump_host()
2928
2928
  @click.pass_context
@@ -3056,7 +3056,7 @@ def vault_replication(ctx):
3056
3056
  @environment_name
3057
3057
  @threaded()
3058
3058
  @binary(["oc", "ssh", "helm"])
3059
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
3059
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
3060
3060
  @internal()
3061
3061
  @use_jump_host()
3062
3062
  @click.option(
@@ -3182,7 +3182,7 @@ def glitchtip_project_alerts(ctx, instance):
3182
3182
  @integration.command(short_help="Glitchtip project dsn as openshift secret.")
3183
3183
  @threaded()
3184
3184
  @binary(["oc", "ssh"])
3185
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
3185
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
3186
3186
  @internal()
3187
3187
  @use_jump_host()
3188
3188
  @click.option("--instance", help="Reconcile just this instance.", default=None)
@@ -3203,7 +3203,7 @@ def glitchtip_project_dsn(ctx, thread_pool_size, internal, use_jump_host, instan
3203
3203
  @integration.command(short_help="Manages Skupper Networks.")
3204
3204
  @threaded()
3205
3205
  @binary(["oc", "ssh"])
3206
- @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSION)
3206
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
3207
3207
  @internal()
3208
3208
  @use_jump_host()
3209
3209
  @click.pass_context
@@ -0,0 +1,77 @@
1
+ """
2
+ Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
3
+ """
4
+ from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
+ from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
+ from enum import Enum # noqa: F401 # pylint: disable=W0611
7
+ from typing import ( # noqa: F401 # pylint: disable=W0611
8
+ Any,
9
+ Optional,
10
+ Union,
11
+ )
12
+
13
+ from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
+ BaseModel,
15
+ Extra,
16
+ Field,
17
+ Json,
18
+ )
19
+
20
+
21
+ DEFINITION = """
22
+ query StateAwsAccount($name: String){
23
+ accounts: awsaccounts_v1 (name: $name)
24
+ {
25
+ name
26
+ resourcesDefaultRegion
27
+ automationToken {
28
+ path
29
+ field
30
+ version
31
+ format
32
+ }
33
+ }
34
+ }
35
+ """
36
+
37
+
38
+ class ConfiguredBaseModel(BaseModel):
39
+ class Config:
40
+ smart_union=True
41
+ extra=Extra.forbid
42
+
43
+
44
+ class VaultSecretV1(ConfiguredBaseModel):
45
+ path: str = Field(..., alias="path")
46
+ field: str = Field(..., alias="field")
47
+ version: Optional[int] = Field(..., alias="version")
48
+ q_format: Optional[str] = Field(..., alias="format")
49
+
50
+
51
+ class AWSAccountV1(ConfiguredBaseModel):
52
+ name: str = Field(..., alias="name")
53
+ resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
54
+ automation_token: VaultSecretV1 = Field(..., alias="automationToken")
55
+
56
+
57
+ class StateAwsAccountQueryData(ConfiguredBaseModel):
58
+ accounts: Optional[list[AWSAccountV1]] = Field(..., alias="accounts")
59
+
60
+
61
+ def query(query_func: Callable, **kwargs: Any) -> StateAwsAccountQueryData:
62
+ """
63
+ This is a convenience function which queries and parses the data into
64
+ concrete types. It should be compatible with most GQL clients.
65
+ You do not have to use it to consume the generated data classes.
66
+ Alternatively, you can also mime and alternate the behavior
67
+ of this function in the caller.
68
+
69
+ Parameters:
70
+ query_func (Callable): Function which queries your GQL Server
71
+ kwargs: optional arguments that will be passed to the query function
72
+
73
+ Returns:
74
+ StateAwsAccountQueryData: queried data parsed into generated classes
75
+ """
76
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
77
+ return StateAwsAccountQueryData(**raw_data)
@@ -0,0 +1,20 @@
1
+ from collections.abc import Callable
2
+
3
+ from reconcile.gql_definitions.common.state_aws_account import (
4
+ AWSAccountV1,
5
+ query,
6
+ )
7
+ from reconcile.utils import gql
8
+
9
+
10
+ def get_state_aws_account(
11
+ name: str, query_func: Callable | None = None
12
+ ) -> AWSAccountV1 | None:
13
+ if not query_func:
14
+ query_func = gql.get_api().query
15
+ if (
16
+ accounts := query(query_func=query_func, variables={"name": name}).accounts
17
+ or []
18
+ ):
19
+ return accounts[0]
20
+ return None
reconcile/utils/binary.py CHANGED
@@ -25,7 +25,7 @@ def binary(binaries=None):
25
25
  return deco_binary
26
26
 
27
27
 
28
- def binary_version(binary, version_args, search_regex, expected_version):
28
+ def binary_version(binary, version_args, search_regex, expected_versions):
29
29
  """Check that a binary exists and is a desired version"""
30
30
 
31
31
  def deco_binary_version(f):
@@ -62,10 +62,10 @@ def binary_version(binary, version_args, search_regex, expected_version):
62
62
  )
63
63
 
64
64
  version = match.group(1)
65
- if version != expected_version:
65
+ if version not in expected_versions:
66
66
  raise Exception(
67
67
  f"Binary version check for binary {binary} failed! "
68
- f"Expected: {expected_version}, found: {version}"
68
+ f"Expected: {expected_versions}, found: {version}"
69
69
  )
70
70
 
71
71
  f(*args, **kwargs)
reconcile/utils/state.py CHANGED
@@ -2,14 +2,16 @@ import json
2
2
  import logging
3
3
  import os
4
4
  from abc import abstractmethod
5
+ from collections.abc import Callable, Mapping
6
+ from types import TracebackType
5
7
  from typing import (
6
8
  Any,
7
9
  Optional,
10
+ Self,
8
11
  )
9
12
 
10
13
  import boto3
11
14
  from botocore.errorfactory import ClientError
12
- from jinja2 import Template
13
15
  from mypy_boto3_s3 import S3Client
14
16
  from pydantic import BaseModel
15
17
 
@@ -23,7 +25,7 @@ from reconcile.typed_queries.app_interface_state_settings import (
23
25
  from reconcile.typed_queries.app_interface_vault_settings import (
24
26
  get_app_interface_vault_settings,
25
27
  )
26
- from reconcile.utils import gql
28
+ from reconcile.typed_queries.get_state_aws_account import get_state_aws_account
27
29
  from reconcile.utils.aws_api import aws_config_file_path
28
30
  from reconcile.utils.secret_reader import (
29
31
  SecretReaderBase,
@@ -35,23 +37,6 @@ class StateInaccessibleException(Exception):
35
37
  pass
36
38
 
37
39
 
38
- STATE_ACCOUNT_QUERY = """
39
- {
40
- accounts: awsaccounts_v1 (name: "{{ name }}")
41
- {
42
- name
43
- resourcesDefaultRegion
44
- automationToken {
45
- path
46
- field
47
- version
48
- format
49
- }
50
- }
51
- }
52
- """
53
-
54
-
55
40
  def init_state(
56
41
  integration: str,
57
42
  secret_reader: Optional[SecretReaderBase] = None,
@@ -99,7 +84,9 @@ class S3ProfileBasedStateConfiguration(S3StateConfiguration):
99
84
  return session.client("s3")
100
85
 
101
86
 
102
- def acquire_state_settings(secret_reader: SecretReaderBase) -> S3StateConfiguration:
87
+ def acquire_state_settings(
88
+ secret_reader: SecretReaderBase, query_func: Callable | None = None
89
+ ) -> S3StateConfiguration:
103
90
  """
104
91
  Finds the settings for the app-interface state provider in the following order:
105
92
 
@@ -183,17 +170,17 @@ def acquire_state_settings(secret_reader: SecretReaderBase) -> S3StateConfigurat
183
170
  logging.debug(
184
171
  f"access state via {state_bucket_account_name} automation token from app-interface"
185
172
  )
186
- account = _get_aws_account_by_name(state_bucket_account_name)
173
+ account = get_state_aws_account(
174
+ state_bucket_account_name, query_func=query_func
175
+ )
187
176
  if not account:
188
177
  raise StateInaccessibleException(
189
178
  f"The AWS account {state_bucket_account_name} that holds the state bucket can't be found in app-interface."
190
179
  )
191
- secret = secret_reader.read_all_secret(
192
- VaultSecret(**account["automationToken"])
193
- )
180
+ secret = secret_reader.read_all_secret(account.automation_token)
194
181
  return S3CredsBasedStateConfiguration(
195
182
  bucket=state_bucket_name,
196
- region=state_bucket_region or account["resourcesDefaultRegion"],
183
+ region=state_bucket_region or account.resources_default_region,
197
184
  access_key_id=secret["aws_access_key_id"],
198
185
  secret_access_key=secret["aws_secret_access_key"],
199
186
  )
@@ -225,14 +212,6 @@ def acquire_state_settings(secret_reader: SecretReaderBase) -> S3StateConfigurat
225
212
  )
226
213
 
227
214
 
228
- def _get_aws_account_by_name(name: str) -> Optional[dict[str, Any]]:
229
- query = Template(STATE_ACCOUNT_QUERY).render(name=name)
230
- aws_accounts = gql.get_api().query(query)["accounts"]
231
- if aws_accounts:
232
- return aws_accounts[0]
233
- return None
234
-
235
-
236
215
  class State:
237
216
  """
238
217
  A state object to be used by stateful integrations.
@@ -264,19 +243,19 @@ class State:
264
243
  f"Bucket {self.bucket} is not accessible - {str(details)}"
265
244
  )
266
245
 
267
- def __enter__(self):
246
+ def __enter__(self) -> Self:
268
247
  return self
269
248
 
270
- def __exit__(self, *exc):
249
+ def __exit__(self, *exc: Any) -> None:
271
250
  self.cleanup()
272
251
 
273
- def cleanup(self):
252
+ def cleanup(self) -> None:
274
253
  """
275
254
  Closes the S3 client
276
255
  """
277
256
  self.client.close()
278
257
 
279
- def exists(self, key):
258
+ def exists(self, key: str) -> bool:
280
259
  """
281
260
  Checks if a key exists in the state.
282
261
 
@@ -290,7 +269,7 @@ class State:
290
269
  exists, _ = self.head(key)
291
270
  return exists
292
271
 
293
- def head(self, key) -> tuple[bool, dict[str, str]]:
272
+ def head(self, key: str) -> tuple[bool, dict[str, str]]:
294
273
  """
295
274
  Checks if a key exists in the state. Returns the metadata of a key in the state.
296
275
 
@@ -315,7 +294,7 @@ class State:
315
294
  f"in bucket {self.bucket} - {str(details)}"
316
295
  )
317
296
 
318
- def ls(self):
297
+ def ls(self) -> list[str]:
319
298
  """
320
299
  Returns a list of keys in the state
321
300
  """
@@ -339,7 +318,13 @@ class State:
339
318
 
340
319
  return [c["Key"].replace(self.state_path, "") for c in contents]
341
320
 
342
- def add(self, key, value=None, metadata=None, force=False):
321
+ def add(
322
+ self,
323
+ key: str,
324
+ value: Any = None,
325
+ metadata: Mapping[str, str] | None = None,
326
+ force: bool = False,
327
+ ) -> None:
343
328
  """
344
329
  Adds a key/value to the state and fails if the key already exists
345
330
 
@@ -354,7 +339,9 @@ class State:
354
339
  raise KeyError(f"[state] key {key} already " f"exists in {self.state_path}")
355
340
  self._set(key, value, metadata=metadata)
356
341
 
357
- def _set(self, key, value, metadata=None):
342
+ def _set(
343
+ self, key: str, value: Any, metadata: Mapping[str, str] | None = None
344
+ ) -> None:
358
345
  self.client.put_object(
359
346
  Bucket=self.bucket,
360
347
  Key=f"{self.state_path}/{key}",
@@ -362,7 +349,7 @@ class State:
362
349
  Metadata=metadata or {},
363
350
  )
364
351
 
365
- def rm(self, key):
352
+ def rm(self, key: str) -> None:
366
353
  """
367
354
  Removes a key from the state and fails if the key does not exists
368
355
 
@@ -374,7 +361,7 @@ class State:
374
361
  raise KeyError(f"[state] key {key} does not exists in {self.state_path}")
375
362
  self.client.delete_object(Bucket=self.bucket, Key=f"{self.state_path}/{key}")
376
363
 
377
- def get(self, key, *args):
364
+ def get(self, key: str, *args: Any) -> Any:
378
365
  """
379
366
  Gets a key value from the state and return the default
380
367
  value or raises and exception if the key does not exist.
@@ -392,7 +379,7 @@ class State:
392
379
  return args[0]
393
380
  raise
394
381
 
395
- def get_all(self, path):
382
+ def get_all(self, path: str) -> dict[str, Any]:
396
383
  """
397
384
  Gets all keys and values from the state in the specified path.
398
385
  """
@@ -402,7 +389,7 @@ class State:
402
389
  if k.startswith(f"/{path}")
403
390
  }
404
391
 
405
- def __getitem__(self, item):
392
+ def __getitem__(self, item: str) -> Any:
406
393
  try:
407
394
  response = self.client.get_object(
408
395
  Bucket=self.bucket, Key=f"{self.state_path}/{item}"
@@ -415,5 +402,55 @@ class State:
415
402
  except json.decoder.JSONDecodeError:
416
403
  raise KeyError(item)
417
404
 
418
- def __setitem__(self, key, value):
405
+ def __setitem__(self, key: str, value: Any) -> None:
419
406
  self._set(key, value)
407
+
408
+ def transaction(self, key: str, value: Any) -> "_TransactionContext":
409
+ """Get a context manager to set the key in the state if no exception occurs.
410
+
411
+ Attention!
412
+
413
+ This is not a locking mechanism. It is a way to ensure that a key is set in the state if no exception occurs.
414
+ This method is not thread-safe nor multi-process-safe! There is no locking mechanism in place.
415
+ """
416
+ return _TransactionContext(self, key, value)
417
+
418
+
419
+ class _TransactionContext:
420
+ """A context manager to set a key in the state if no exception occurs."""
421
+
422
+ def __init__(
423
+ self,
424
+ state: State,
425
+ key: str,
426
+ value: Any,
427
+ ):
428
+ self.state = state
429
+ self.key = key
430
+ self.value = value
431
+
432
+ def __enter__(self) -> bool:
433
+ """Return True if the key exists in the state, False otherwise.
434
+
435
+ Cache the previous value to avoid unnecessary updates.
436
+ """
437
+ self._previous_value = None
438
+ try:
439
+ self._previous_value = self.state[self.key]
440
+ return True
441
+ except KeyError:
442
+ return False
443
+
444
+ def __exit__(
445
+ self,
446
+ exc_type: type[BaseException] | None,
447
+ exc_value: BaseException | None,
448
+ traceback: TracebackType | None,
449
+ ) -> None:
450
+ if exc_type:
451
+ # if an exception occurred, we don't want to write to the state
452
+ return
453
+ if self._previous_value == self.value:
454
+ # if the value didn't change, we don't want to write to the state
455
+ return
456
+ self.state[self.key] = self.value