qontract-reconcile 0.10.2.dev202__py3-none-any.whl → 0.10.2.dev204__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.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev202
3
+ Version: 0.10.2.dev204
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -9,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=O0JX2N5kNRKs3u2xzu4NNrI6p0ag5JWy3MTsv
9
9
  reconcile/aws_support_cases_sos.py,sha256=PDhilxQ4TBxVnxUPIUdTbKEaNUI0wzPiEsB91oHT2fY,3384
10
10
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
11
11
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
12
- reconcile/cli.py,sha256=-WTtuEtxDuHdZ-GIGtklu4p6r8YdR7QfRqdK6rHl2gY,108163
12
+ reconcile/cli.py,sha256=e11ElvZwncpUcx3AwxajGLUfrLuAWm_4fOy_jPMm_oM,108741
13
13
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=al7m8EgnnYx90rY1REryW3byN_ItfJfAzEeLtjbCfi0,4921
14
14
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
15
15
  reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
@@ -72,6 +72,7 @@ reconcile/openshift_prometheus_rules.py,sha256=onowXab248zmHH8SbYDTc1W1bl7JiqRFU
72
72
  reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
73
73
  reconcile/openshift_resources.py,sha256=I2nO_C37mG3rfyGrd4cGwN3mVseVGuTAHAyhFzLyqF4,1518
74
74
  reconcile/openshift_resources_base.py,sha256=3HudPdM7EE0HNWUn1eu0O20Ij25fqGisaDBMVvTk1fk,41768
75
+ reconcile/openshift_rhcs_certs.py,sha256=snLx33sX2cj2TPGRgrEGDV6Ofm17IougnxT0T-5FNoA,9448
75
76
  reconcile/openshift_rolebindings.py,sha256=9mlJ2FjWUoH-rsjtasreA_hV-K5Z_YR00qR_RR60OZM,6555
76
77
  reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
77
78
  reconcile/openshift_saas_deploy.py,sha256=T1dvb9zajisaJNjbnR6-AZHU-itscHtr4oCqLj8KCK0,13037
@@ -226,7 +227,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
226
227
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
227
228
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
228
229
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
- reconcile/gql_definitions/introspection.json,sha256=JiCvliRsiUfBN70aVfZ4JcW6qKXRtgHGiVmoWd7B_GI,2327754
230
+ reconcile/gql_definitions/introspection.json,sha256=Go8Ep6qN2COkqnWsqQLfiGDS1TbQFyK_fp5TiM0ghpo,2334795
230
231
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
231
232
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
232
233
  reconcile/gql_definitions/acs/acs_policies.py,sha256=Ygpfl2-VkYLSlJvHgp_dJBfb66K_Rwfdfpsa18w1v1s,4338
@@ -295,6 +296,7 @@ reconcile/gql_definitions/common/pipeline_providers.py,sha256=9rpsqPuvj82B4ki56x
295
296
  reconcile/gql_definitions/common/quay_instances.py,sha256=toBkdYYVTmEafezAHZKgaW-mQ29xEW6jeronzsAlNyI,1786
296
297
  reconcile/gql_definitions/common/quay_orgs.py,sha256=NhA8kqvVUDbrsryEvEL5mlIv5R3T4XNhSRXtfL_yptY,1788
297
298
  reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
299
+ reconcile/gql_definitions/common/rhcs_provider_settings.py,sha256=x3OmYjMCr9EzfkmLmbdx9zEKzqE9IczE8lcyzf_v9JU,1969
298
300
  reconcile/gql_definitions/common/saas_files.py,sha256=d1L_S5LgCMa4QuAqZGQYTWb5L_nPCOxSEjU4O__OeBU,17728
299
301
  reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=4VYP2VbwY8WVwtSFk2-jsUNhSmRD3X4FWKxetOKvmd0,2835
300
302
  reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
@@ -401,6 +403,8 @@ reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py,sha256=47D
401
403
  reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py,sha256=FeraeQThHl2UbhTuCPDwm3ltczF_jfZn5E3Squ8-znw,3313
402
404
  reconcile/gql_definitions/quay_membership/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
403
405
  reconcile/gql_definitions/quay_membership/quay_membership.py,sha256=MKBkrE-1YYelaAAxOdpqUwCo45kOVC8q29vXArqK_zM,3075
406
+ reconcile/gql_definitions/rhcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
407
+ reconcile/gql_definitions/rhcs/certs.py,sha256=8ba9GZVY70ppekuxrMjE4wm6WqcMW2IFawjhWvxHrmI,4677
404
408
  reconcile/gql_definitions/rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
405
409
  reconcile/gql_definitions/rhidp/organizations.py,sha256=dW9y3ewFu3E-DFrZAi_SEewHYR0MWYeOB52vwnVcq5E,2580
406
410
  reconcile/gql_definitions/service_dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -571,6 +575,7 @@ reconcile/typed_queries/pagerduty_instances.py,sha256=zxCNxMak4iikryePaRi71lTADV
571
575
  reconcile/typed_queries/quay.py,sha256=3IMy9jjHF2f9t47EXZOQVA3p0nFkWFhaFhxhvib-71o,644
572
576
  reconcile/typed_queries/repos.py,sha256=8A93dKDt6igT4ClqMjt7YUTsoP4qh1Wnm0W3xsMgj48,824
573
577
  reconcile/typed_queries/reserved_networks.py,sha256=XY9y3amtIQT0n06O0Toubqr_UmylJ2ELAv9-BJCK890,345
578
+ reconcile/typed_queries/rhcs_provider_settings.py,sha256=fxD84CPCiH25_qc02p48LOIJHv39Wlc1VwOhs9ZVsKk,743
574
579
  reconcile/typed_queries/saas_files.py,sha256=SOE36sWPBcuaRmEaNxXCQZMQdJiUZX8_A92o42XwHQA,14141
575
580
  reconcile/typed_queries/slack.py,sha256=r30lspctHloyygPn8_DVybxPwUWwiBpvBRRXiTVcQYk,251
576
581
  reconcile/typed_queries/slo_documents.py,sha256=YMdox_-lBRqrdxamPhdnUlRTY_Ro35ptsupq7OaynUQ,362
@@ -597,7 +602,7 @@ reconcile/unleash_feature_toggles/integration.py,sha256=nx7BhtzCsTfPbOp60vI5MkNw
597
602
  reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
598
603
  reconcile/utils/aggregated_list.py,sha256=_9UeaS1TWbJsGIESvXlzzK-omPI2lMMcCsoqc9LBclc,4022
599
604
  reconcile/utils/amtool.py,sha256=Ng5VVNCiPYEK67eDjIwfuuTLs5JsfltCwt6w5UfXbcY,2289
600
- reconcile/utils/aws_api.py,sha256=_Cr3zBPqpk1iTJxfWcbT7-8d3WiJihSA2zHdXX68EI4,80479
605
+ reconcile/utils/aws_api.py,sha256=Xrt4uukct0u5kkQBvl0azquF8JEbrb_aFtlBNEL2G2E,81488
601
606
  reconcile/utils/aws_helper.py,sha256=8PvDR17ntAGX3bBzlTIxDuENl2rkK-RECsNYKm2_DZw,2955
602
607
  reconcile/utils/batches.py,sha256=TtEm64a8lWhFuNbUVpFEmXVdU2Q0sTBrP_I0Cjbgh7g,320
603
608
  reconcile/utils/binary.py,sha256=lSIevhilMeoGMePPHD7A-pxe45LVpBT0LksecYbM-EA,2477
@@ -658,6 +663,7 @@ reconcile/utils/quay_mirror.py,sha256=dpWCNv5lITwIk6Q9RkmqaQKHNk_JPy27UQEribJ7E-
658
663
  reconcile/utils/raw_github_api.py,sha256=2WKtE8ABYYB9UGOAh9N_kLkksBWL3320Z2_scteZddI,2805
659
664
  reconcile/utils/repo_owners.py,sha256=P0QX6F0oB8wYA08yiyzhYUiBtU57iIK_PsxbzKENbKM,6571
660
665
  reconcile/utils/rest_api_base.py,sha256=MT7tp6CQO2S5aKfVOzw_hipWg7wAGoOqkm4qurI1hEU,4342
666
+ reconcile/utils/rhcsv2_certs.py,sha256=nS0NJGI7pzEvVd0F84_KQ9hsxmslz-Db7Y2Pt-1E0x8,2248
661
667
  reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
662
668
  reconcile/utils/secret_reader.py,sha256=MaP56KZaAE35EyYbgAitdm6fUSxdzWeGFSOym9qiZkw,10206
663
669
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
@@ -670,7 +676,7 @@ reconcile/utils/state.py,sha256=az4tBmZ0EdbFcAGiBVUxs3cr2-BVWsuDQiNTvjjQq8s,1637
670
676
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
671
677
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
672
678
  reconcile/utils/terraform_client.py,sha256=IDlrNvGEc2i6ElZIL_fzaJEad1nRC3DkP9_VXhJXmU0,37329
673
- reconcile/utils/terrascript_aws_client.py,sha256=gyBUJK89FDYEmb0QyPvD4pCgc5zjlQ6yp3PO6nxCJgI,292443
679
+ reconcile/utils/terrascript_aws_client.py,sha256=RkiYjRietHFNXtfA1-WEZ1lZbJFBA_XCtTOsZUij5VM,292360
674
680
  reconcile/utils/three_way_diff_strategy.py,sha256=oQcHXd9LVhirJfoaOBoHUYuZVGfyL2voKr6KVI34zZE,4833
675
681
  reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
676
682
  reconcile/utils/vault.py,sha256=aSA8l9cJlPUHpChFGl27nSY-Mpq9FMjBo7Dcgb1BVfM,15036
@@ -809,7 +815,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
809
815
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
810
816
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
811
817
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
812
- qontract_reconcile-0.10.2.dev202.dist-info/METADATA,sha256=CYYvFFrH_On8hDff-SoAevyuCTPhipq1U_8ZnUL9E-c,24555
813
- qontract_reconcile-0.10.2.dev202.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
814
- qontract_reconcile-0.10.2.dev202.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
815
- qontract_reconcile-0.10.2.dev202.dist-info/RECORD,,
818
+ qontract_reconcile-0.10.2.dev204.dist-info/METADATA,sha256=oPtlkzclWzhdxuM8i4TJB0ykd-vcRwVOk2orPS4I_Zw,24555
819
+ qontract_reconcile-0.10.2.dev204.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
820
+ qontract_reconcile-0.10.2.dev204.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
821
+ qontract_reconcile-0.10.2.dev204.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -1739,6 +1739,27 @@ def openshift_vault_secrets(
1739
1739
  )
1740
1740
 
1741
1741
 
1742
+ @integration.command(short_help="Manages OpenShift Secrets for RHCS certificates")
1743
+ @threaded()
1744
+ @binary(["oc", "ssh"])
1745
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1746
+ @internal()
1747
+ @use_jump_host()
1748
+ @cluster_name
1749
+ @click.pass_context
1750
+ def openshift_rhcs_certs(ctx, thread_pool_size, internal, use_jump_host, cluster_name):
1751
+ import reconcile.openshift_rhcs_certs
1752
+
1753
+ run_integration(
1754
+ reconcile.openshift_rhcs_certs,
1755
+ ctx.obj,
1756
+ thread_pool_size,
1757
+ internal,
1758
+ use_jump_host,
1759
+ cluster_name=cluster_name,
1760
+ )
1761
+
1762
+
1742
1763
  @integration.command(short_help="Manages OpenShift Routes.")
1743
1764
  @threaded()
1744
1765
  @binary(["oc", "ssh"])
@@ -0,0 +1,68 @@
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 RhcsProviderSettings {
23
+ settings: app_interface_settings_v1 {
24
+ rhcsProvider {
25
+ url
26
+ vaultBasePath
27
+ }
28
+ }
29
+ }
30
+ """
31
+
32
+
33
+ class ConfiguredBaseModel(BaseModel):
34
+ class Config:
35
+ smart_union=True
36
+ extra=Extra.forbid
37
+
38
+
39
+ class RhcsProviderSettingsV1(ConfiguredBaseModel):
40
+ url: str = Field(..., alias="url")
41
+ vault_base_path: str = Field(..., alias="vaultBasePath")
42
+
43
+
44
+ class AppInterfaceSettingsV1(ConfiguredBaseModel):
45
+ rhcs_provider: Optional[RhcsProviderSettingsV1] = Field(..., alias="rhcsProvider")
46
+
47
+
48
+ class RhcsProviderSettingsQueryData(ConfiguredBaseModel):
49
+ settings: Optional[list[AppInterfaceSettingsV1]] = Field(..., alias="settings")
50
+
51
+
52
+ def query(query_func: Callable, **kwargs: Any) -> RhcsProviderSettingsQueryData:
53
+ """
54
+ This is a convenience function which queries and parses the data into
55
+ concrete types. It should be compatible with most GQL clients.
56
+ You do not have to use it to consume the generated data classes.
57
+ Alternatively, you can also mime and alternate the behavior
58
+ of this function in the caller.
59
+
60
+ Parameters:
61
+ query_func (Callable): Function which queries your GQL Server
62
+ kwargs: optional arguments that will be passed to the query function
63
+
64
+ Returns:
65
+ RhcsProviderSettingsQueryData: queried data parsed into generated classes
66
+ """
67
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
68
+ return RhcsProviderSettingsQueryData(**raw_data)
@@ -4362,6 +4362,18 @@
4362
4362
  },
4363
4363
  "isDeprecated": false,
4364
4364
  "deprecationReason": null
4365
+ },
4366
+ {
4367
+ "name": "rhcsProvider",
4368
+ "description": null,
4369
+ "args": [],
4370
+ "type": {
4371
+ "kind": "OBJECT",
4372
+ "name": "RhcsProviderSettings_v1",
4373
+ "ofType": null
4374
+ },
4375
+ "isDeprecated": false,
4376
+ "deprecationReason": null
4365
4377
  }
4366
4378
  ],
4367
4379
  "inputFields": null,
@@ -24806,6 +24818,11 @@
24806
24818
  "name": "NamespaceOpenshiftResourceResourceTemplate_v1",
24807
24819
  "ofType": null
24808
24820
  },
24821
+ {
24822
+ "kind": "OBJECT",
24823
+ "name": "NamespaceOpenshiftResourceRhcsCert_v1",
24824
+ "ofType": null
24825
+ },
24809
24826
  {
24810
24827
  "kind": "OBJECT",
24811
24828
  "name": "NamespaceOpenshiftResourceRoute_v1",
@@ -26558,6 +26575,49 @@
26558
26575
  "enumValues": null,
26559
26576
  "possibleTypes": null
26560
26577
  },
26578
+ {
26579
+ "kind": "OBJECT",
26580
+ "name": "RhcsProviderSettings_v1",
26581
+ "description": null,
26582
+ "fields": [
26583
+ {
26584
+ "name": "url",
26585
+ "description": null,
26586
+ "args": [],
26587
+ "type": {
26588
+ "kind": "NON_NULL",
26589
+ "name": null,
26590
+ "ofType": {
26591
+ "kind": "SCALAR",
26592
+ "name": "String",
26593
+ "ofType": null
26594
+ }
26595
+ },
26596
+ "isDeprecated": false,
26597
+ "deprecationReason": null
26598
+ },
26599
+ {
26600
+ "name": "vaultBasePath",
26601
+ "description": null,
26602
+ "args": [],
26603
+ "type": {
26604
+ "kind": "NON_NULL",
26605
+ "name": null,
26606
+ "ofType": {
26607
+ "kind": "SCALAR",
26608
+ "name": "String",
26609
+ "ofType": null
26610
+ }
26611
+ },
26612
+ "isDeprecated": false,
26613
+ "deprecationReason": null
26614
+ }
26615
+ ],
26616
+ "inputFields": null,
26617
+ "interfaces": [],
26618
+ "enumValues": null,
26619
+ "possibleTypes": null
26620
+ },
26561
26621
  {
26562
26622
  "kind": "OBJECT",
26563
26623
  "name": "AppInterfaceEmail_v1",
@@ -40971,6 +41031,111 @@
40971
41031
  "enumValues": null,
40972
41032
  "possibleTypes": null
40973
41033
  },
41034
+ {
41035
+ "kind": "OBJECT",
41036
+ "name": "NamespaceOpenshiftResourceRhcsCert_v1",
41037
+ "description": null,
41038
+ "fields": [
41039
+ {
41040
+ "name": "provider",
41041
+ "description": null,
41042
+ "args": [],
41043
+ "type": {
41044
+ "kind": "NON_NULL",
41045
+ "name": null,
41046
+ "ofType": {
41047
+ "kind": "SCALAR",
41048
+ "name": "String",
41049
+ "ofType": null
41050
+ }
41051
+ },
41052
+ "isDeprecated": false,
41053
+ "deprecationReason": null
41054
+ },
41055
+ {
41056
+ "name": "secret_name",
41057
+ "description": null,
41058
+ "args": [],
41059
+ "type": {
41060
+ "kind": "NON_NULL",
41061
+ "name": null,
41062
+ "ofType": {
41063
+ "kind": "SCALAR",
41064
+ "name": "String",
41065
+ "ofType": null
41066
+ }
41067
+ },
41068
+ "isDeprecated": false,
41069
+ "deprecationReason": null
41070
+ },
41071
+ {
41072
+ "name": "service_account_name",
41073
+ "description": null,
41074
+ "args": [],
41075
+ "type": {
41076
+ "kind": "NON_NULL",
41077
+ "name": null,
41078
+ "ofType": {
41079
+ "kind": "SCALAR",
41080
+ "name": "String",
41081
+ "ofType": null
41082
+ }
41083
+ },
41084
+ "isDeprecated": false,
41085
+ "deprecationReason": null
41086
+ },
41087
+ {
41088
+ "name": "service_account_password",
41089
+ "description": null,
41090
+ "args": [],
41091
+ "type": {
41092
+ "kind": "NON_NULL",
41093
+ "name": null,
41094
+ "ofType": {
41095
+ "kind": "OBJECT",
41096
+ "name": "VaultSecret_v1",
41097
+ "ofType": null
41098
+ }
41099
+ },
41100
+ "isDeprecated": false,
41101
+ "deprecationReason": null
41102
+ },
41103
+ {
41104
+ "name": "auto_renew_threshold_days",
41105
+ "description": null,
41106
+ "args": [],
41107
+ "type": {
41108
+ "kind": "SCALAR",
41109
+ "name": "Int",
41110
+ "ofType": null
41111
+ },
41112
+ "isDeprecated": false,
41113
+ "deprecationReason": null
41114
+ },
41115
+ {
41116
+ "name": "annotations",
41117
+ "description": null,
41118
+ "args": [],
41119
+ "type": {
41120
+ "kind": "SCALAR",
41121
+ "name": "JSON",
41122
+ "ofType": null
41123
+ },
41124
+ "isDeprecated": false,
41125
+ "deprecationReason": null
41126
+ }
41127
+ ],
41128
+ "inputFields": null,
41129
+ "interfaces": [
41130
+ {
41131
+ "kind": "INTERFACE",
41132
+ "name": "NamespaceOpenshiftResource_v1",
41133
+ "ofType": null
41134
+ }
41135
+ ],
41136
+ "enumValues": null,
41137
+ "possibleTypes": null
41138
+ },
40974
41139
  {
40975
41140
  "kind": "OBJECT",
40976
41141
  "name": "NamespaceOpenshiftResourceRoute_v1",
File without changes
@@ -0,0 +1,158 @@
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
+ from reconcile.gql_definitions.fragments.jumphost_common_fields import CommonJumphostFields
21
+ from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
22
+
23
+
24
+ DEFINITION = """
25
+ fragment CommonJumphostFields on ClusterJumpHost_v1 {
26
+ hostname
27
+ knownHosts
28
+ user
29
+ port
30
+ remotePort
31
+ identity {
32
+ ... VaultSecret
33
+ }
34
+ }
35
+
36
+ fragment VaultSecret on VaultSecret_v1 {
37
+ path
38
+ field
39
+ version
40
+ format
41
+ }
42
+
43
+ query RhcsCerts {
44
+ namespaces: namespaces_v1 {
45
+ name
46
+ delete
47
+ clusterAdmin
48
+ openshiftResources {
49
+ provider
50
+ ... on NamespaceOpenshiftResourceRhcsCert_v1 {
51
+ secret_name
52
+ service_account_name
53
+ service_account_password {
54
+ ... on VaultSecret_v1 {
55
+ path
56
+ field
57
+ version
58
+ }
59
+ }
60
+ auto_renew_threshold_days
61
+ annotations
62
+ }
63
+ }
64
+ cluster {
65
+ name
66
+ serverUrl
67
+ insecureSkipTLSVerify
68
+ jumpHost {
69
+ ... CommonJumphostFields
70
+ }
71
+ automationToken {
72
+ ... VaultSecret
73
+ }
74
+ clusterAdminAutomationToken {
75
+ ... VaultSecret
76
+ }
77
+ internal
78
+ disable {
79
+ integrations
80
+ }
81
+ }
82
+ }
83
+ }
84
+ """
85
+
86
+
87
+ class ConfiguredBaseModel(BaseModel):
88
+ class Config:
89
+ smart_union=True
90
+ extra=Extra.forbid
91
+
92
+
93
+ class NamespaceOpenshiftResourceV1(ConfiguredBaseModel):
94
+ provider: str = Field(..., alias="provider")
95
+
96
+
97
+ class VaultSecretV1(ConfiguredBaseModel):
98
+ ...
99
+
100
+
101
+ class VaultSecretV1_VaultSecretV1(VaultSecretV1):
102
+ path: str = Field(..., alias="path")
103
+ field: str = Field(..., alias="field")
104
+ version: Optional[int] = Field(..., alias="version")
105
+
106
+
107
+ class NamespaceOpenshiftResourceRhcsCertV1(NamespaceOpenshiftResourceV1):
108
+ secret_name: str = Field(..., alias="secret_name")
109
+ service_account_name: str = Field(..., alias="service_account_name")
110
+ service_account_password: Union[VaultSecretV1_VaultSecretV1, VaultSecretV1] = Field(..., alias="service_account_password")
111
+ auto_renew_threshold_days: Optional[int] = Field(..., alias="auto_renew_threshold_days")
112
+ annotations: Optional[Json] = Field(..., alias="annotations")
113
+
114
+
115
+ class DisableClusterAutomationsV1(ConfiguredBaseModel):
116
+ integrations: Optional[list[str]] = Field(..., alias="integrations")
117
+
118
+
119
+ class ClusterV1(ConfiguredBaseModel):
120
+ name: str = Field(..., alias="name")
121
+ server_url: str = Field(..., alias="serverUrl")
122
+ insecure_skip_tls_verify: Optional[bool] = Field(..., alias="insecureSkipTLSVerify")
123
+ jump_host: Optional[CommonJumphostFields] = Field(..., alias="jumpHost")
124
+ automation_token: Optional[VaultSecret] = Field(..., alias="automationToken")
125
+ cluster_admin_automation_token: Optional[VaultSecret] = Field(..., alias="clusterAdminAutomationToken")
126
+ internal: Optional[bool] = Field(..., alias="internal")
127
+ disable: Optional[DisableClusterAutomationsV1] = Field(..., alias="disable")
128
+
129
+
130
+ class NamespaceV1(ConfiguredBaseModel):
131
+ name: str = Field(..., alias="name")
132
+ delete: Optional[bool] = Field(..., alias="delete")
133
+ cluster_admin: Optional[bool] = Field(..., alias="clusterAdmin")
134
+ openshift_resources: Optional[list[Union[NamespaceOpenshiftResourceRhcsCertV1, NamespaceOpenshiftResourceV1]]] = Field(..., alias="openshiftResources")
135
+ cluster: ClusterV1 = Field(..., alias="cluster")
136
+
137
+
138
+ class RhcsCertsQueryData(ConfiguredBaseModel):
139
+ namespaces: Optional[list[NamespaceV1]] = Field(..., alias="namespaces")
140
+
141
+
142
+ def query(query_func: Callable, **kwargs: Any) -> RhcsCertsQueryData:
143
+ """
144
+ This is a convenience function which queries and parses the data into
145
+ concrete types. It should be compatible with most GQL clients.
146
+ You do not have to use it to consume the generated data classes.
147
+ Alternatively, you can also mime and alternate the behavior
148
+ of this function in the caller.
149
+
150
+ Parameters:
151
+ query_func (Callable): Function which queries your GQL Server
152
+ kwargs: optional arguments that will be passed to the query function
153
+
154
+ Returns:
155
+ RhcsCertsQueryData: queried data parsed into generated classes
156
+ """
157
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
158
+ return RhcsCertsQueryData(**raw_data)
@@ -0,0 +1,269 @@
1
+ import logging
2
+ import sys
3
+ import time
4
+ from collections.abc import Callable, Iterable, Mapping
5
+ from typing import Any
6
+
7
+ import reconcile.openshift_base as ob
8
+ import reconcile.openshift_resources_base as orb
9
+ from reconcile.gql_definitions.rhcs.certs import (
10
+ NamespaceOpenshiftResourceRhcsCertV1,
11
+ NamespaceV1,
12
+ )
13
+ from reconcile.gql_definitions.rhcs.certs import (
14
+ query as rhcs_certs_query,
15
+ )
16
+ from reconcile.typed_queries.app_interface_vault_settings import (
17
+ get_app_interface_vault_settings,
18
+ )
19
+ from reconcile.typed_queries.rhcs_provider_settings import get_rhcs_provider_settings
20
+ from reconcile.utils import gql, metrics
21
+ from reconcile.utils.disabled_integrations import integration_is_enabled
22
+ from reconcile.utils.metrics import GaugeMetric, normalize_integration_name
23
+ from reconcile.utils.oc_map import init_oc_map_from_namespaces
24
+ from reconcile.utils.openshift_resource import OpenshiftResource as OR
25
+ from reconcile.utils.openshift_resource import (
26
+ ResourceInventory,
27
+ base64_encode_secret_field_value,
28
+ )
29
+ from reconcile.utils.rhcsv2_certs import RhcsV2Cert, generate_cert
30
+ from reconcile.utils.runtime.integration import DesiredStateShardConfig
31
+ from reconcile.utils.secret_reader import create_secret_reader
32
+ from reconcile.utils.semver_helper import make_semver
33
+ from reconcile.utils.vault import SecretNotFound, _VaultClient
34
+
35
+ QONTRACT_INTEGRATION = "openshift-rhcs-certs"
36
+ QONTRACT_INTEGRATION_VERSION = make_semver(1, 9, 3)
37
+ PROVIDERS = ["rhcs-cert"]
38
+
39
+
40
+ def desired_state_shard_config() -> DesiredStateShardConfig:
41
+ return DesiredStateShardConfig(
42
+ shard_arg_name="cluster_name",
43
+ shard_path_selectors={
44
+ "state.*.shard",
45
+ },
46
+ sharded_run_review=lambda proposal: len(proposal.proposed_shards) <= 2,
47
+ )
48
+
49
+
50
+ class OpenshiftRhcsCertExpiration(GaugeMetric):
51
+ """Expiration timestamp of RHCS certificate stored in Vault."""
52
+
53
+ integration: str = normalize_integration_name(QONTRACT_INTEGRATION)
54
+ cert_name: str
55
+ cluster: str
56
+ namespace: str
57
+
58
+ @classmethod
59
+ def name(cls) -> str:
60
+ return "qontract_reconcile_rhcs_cert_expiration_timestamp"
61
+
62
+
63
+ def get_namespaces_with_rhcs_certs(
64
+ query_func: Callable, cluster_name: Iterable[str] | None = None
65
+ ) -> list[NamespaceV1]:
66
+ return [
67
+ ns
68
+ for ns in rhcs_certs_query(query_func=query_func).namespaces or []
69
+ if integration_is_enabled(QONTRACT_INTEGRATION, ns.cluster)
70
+ and not bool(ns.delete)
71
+ and (not cluster_name or ns.cluster.name in cluster_name)
72
+ and any(
73
+ isinstance(r, NamespaceOpenshiftResourceRhcsCertV1)
74
+ for r in ns.openshift_resources or []
75
+ )
76
+ ]
77
+
78
+
79
+ def construct_rhcs_cert_oc_secret(
80
+ secret_name: str, cert: Mapping[str, Any], annotations: Mapping[str, str]
81
+ ) -> OR:
82
+ body: dict[str, Any] = {
83
+ "apiVersion": "v1",
84
+ "kind": "Secret",
85
+ "type": "Opaque",
86
+ "metadata": {"name": secret_name, "annotations": annotations},
87
+ }
88
+ for k, v in cert.items():
89
+ v = base64_encode_secret_field_value(v)
90
+ body.setdefault("data", {})[k] = v
91
+ return OR(body, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION)
92
+
93
+
94
+ def cert_expires_within_threshold(
95
+ ns: NamespaceV1,
96
+ cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
97
+ vault_cert_secret: Mapping[str, Any],
98
+ ) -> bool:
99
+ auto_renew_threshold_days = cert_resource.auto_renew_threshold_days or 7
100
+ expires_in = int(vault_cert_secret["expiration_timestamp"]) - time.time()
101
+ threshold_in_seconds = 60 * 60 * 24 * auto_renew_threshold_days
102
+ if expires_in < threshold_in_seconds:
103
+ logging.info(
104
+ f"Existing cert expires within threshold: cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}', threshold='{auto_renew_threshold_days} days'"
105
+ )
106
+ return True
107
+ return False
108
+
109
+
110
+ def get_vault_cert_secret(
111
+ ns: NamespaceV1,
112
+ cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
113
+ vault: _VaultClient,
114
+ vault_base_path: str,
115
+ ) -> dict | None:
116
+ vault_cert_secret = None
117
+ try:
118
+ vault_cert_secret = vault.read_all({
119
+ "path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}"
120
+ })
121
+ except SecretNotFound:
122
+ logging.info(
123
+ f"No existing cert found for cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}', threshold='{cert_resource.auto_renew_threshold_days} days'"
124
+ )
125
+ return vault_cert_secret
126
+
127
+
128
+ def generate_vault_cert_secret(
129
+ dry_run: bool,
130
+ ns: NamespaceV1,
131
+ cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
132
+ vault: _VaultClient,
133
+ vault_base_path: str,
134
+ cert_provider_url: str,
135
+ ) -> dict:
136
+ logging.info(
137
+ f"Creating cert with service account credentials for '{cert_resource.service_account_name}'. cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}'"
138
+ )
139
+ sa_password = vault.read(cert_resource.service_account_password.dict())
140
+ if dry_run:
141
+ rhcs_cert = RhcsV2Cert(
142
+ certificate="PLACEHOLDER_CERT",
143
+ private_key="PLACEHOLDER_PRIVATE_KEY",
144
+ expiration_timestamp=int(time.time()),
145
+ )
146
+ else:
147
+ try:
148
+ rhcs_cert = generate_cert(
149
+ cert_provider_url,
150
+ cert_resource.service_account_name,
151
+ sa_password,
152
+ )
153
+ except ValueError as e:
154
+ raise Exception(
155
+ f"Certificate generation failed using service account '{cert_resource.service_account_name}': {e}"
156
+ ) from None
157
+ logging.info(
158
+ f"Writing cert details to Vault at {vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}"
159
+ )
160
+ vault.write(
161
+ secret={
162
+ "data": rhcs_cert.dict(),
163
+ "path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}",
164
+ },
165
+ decode_base64=False,
166
+ )
167
+ return rhcs_cert.dict()
168
+
169
+
170
+ def fetch_openshift_resource_for_cert_resource(
171
+ dry_run: bool,
172
+ ns: NamespaceV1,
173
+ cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
174
+ vault: _VaultClient,
175
+ vault_base_path: str,
176
+ cert_provider_url: str,
177
+ ) -> OR:
178
+ vault_cert_secret = get_vault_cert_secret(ns, cert_resource, vault, vault_base_path)
179
+ if vault_cert_secret is None or cert_expires_within_threshold(
180
+ ns, cert_resource, vault_cert_secret
181
+ ):
182
+ vault_cert_secret = generate_vault_cert_secret(
183
+ dry_run, ns, cert_resource, vault, vault_base_path, cert_provider_url
184
+ )
185
+
186
+ if not dry_run:
187
+ metrics.set_gauge(
188
+ OpenshiftRhcsCertExpiration(
189
+ cert_name=cert_resource.secret_name,
190
+ namespace=ns.name,
191
+ cluster=ns.cluster.name,
192
+ ),
193
+ int(vault_cert_secret["expiration_timestamp"]),
194
+ )
195
+
196
+ return construct_rhcs_cert_oc_secret(
197
+ secret_name=cert_resource.secret_name,
198
+ cert=vault_cert_secret,
199
+ annotations=cert_resource.annotations or {},
200
+ )
201
+
202
+
203
+ def fetch_desired_state(
204
+ dry_run: bool,
205
+ namespaces: list[NamespaceV1],
206
+ ri: ResourceInventory,
207
+ query_func: Callable,
208
+ ) -> None:
209
+ vault = _VaultClient()
210
+ cert_provider = get_rhcs_provider_settings(query_func=query_func)
211
+
212
+ for ns in namespaces:
213
+ for cert_resource in ns.openshift_resources or []:
214
+ if isinstance(cert_resource, NamespaceOpenshiftResourceRhcsCertV1):
215
+ ri.add_desired_resource(
216
+ cluster=ns.cluster.name,
217
+ namespace=ns.name,
218
+ resource=fetch_openshift_resource_for_cert_resource(
219
+ dry_run,
220
+ ns,
221
+ cert_resource,
222
+ vault,
223
+ f"{cert_provider.vault_base_path}/{QONTRACT_INTEGRATION}",
224
+ cert_provider.url,
225
+ ),
226
+ )
227
+
228
+
229
+ def run(
230
+ dry_run: bool,
231
+ thread_pool_size: int = 10,
232
+ internal: bool | None = None,
233
+ use_jump_host: bool = True,
234
+ cluster_name: Iterable[str] | None = None,
235
+ ) -> None:
236
+ gql_api = gql.get_api()
237
+ vault_settings = get_app_interface_vault_settings()
238
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
239
+ namespaces = get_namespaces_with_rhcs_certs(gql_api.query, cluster_name)
240
+ oc_map = init_oc_map_from_namespaces(
241
+ namespaces=namespaces,
242
+ integration=QONTRACT_INTEGRATION,
243
+ secret_reader=secret_reader,
244
+ internal=internal,
245
+ use_jump_host=use_jump_host,
246
+ thread_pool_size=thread_pool_size,
247
+ )
248
+ ri = ResourceInventory()
249
+ state_specs = ob.init_specs_to_fetch(
250
+ ri,
251
+ oc_map,
252
+ namespaces=[ns.dict(by_alias=True) for ns in namespaces],
253
+ override_managed_types=["Secret"],
254
+ )
255
+ for spec in state_specs:
256
+ if isinstance(spec, ob.CurrentStateSpec):
257
+ orb.fetch_current_state(
258
+ spec.oc,
259
+ ri,
260
+ spec.cluster,
261
+ spec.namespace,
262
+ spec.kind,
263
+ spec.resource_names,
264
+ )
265
+ fetch_desired_state(dry_run, namespaces, ri, gql_api.query)
266
+ ob.realize_data(dry_run, oc_map, ri, thread_pool_size)
267
+ ob.publish_metrics(ri, QONTRACT_INTEGRATION)
268
+ if ri.has_error_registered():
269
+ sys.exit(1)
@@ -0,0 +1,21 @@
1
+ from collections.abc import Callable
2
+
3
+ from reconcile.gql_definitions.common.rhcs_provider_settings import (
4
+ RhcsProviderSettingsV1,
5
+ query,
6
+ )
7
+ from reconcile.utils import gql
8
+ from reconcile.utils.exceptions import AppInterfaceSettingsError
9
+
10
+
11
+ def get_rhcs_provider_settings(
12
+ query_func: Callable | None = None,
13
+ ) -> RhcsProviderSettingsV1:
14
+ """Returns App Interface Settings and raises err if none are found"""
15
+ if not query_func:
16
+ query_func = gql.get_api().query
17
+ data = query(query_func)
18
+ if data.settings and len(data.settings) == 1:
19
+ if data.settings[0].rhcs_provider:
20
+ return data.settings[0].rhcs_provider
21
+ raise AppInterfaceSettingsError("RHCS provider settings not uniquely defined.")
@@ -42,6 +42,7 @@ if TYPE_CHECKING:
42
42
  FilterTypeDef,
43
43
  ImageTypeDef,
44
44
  LaunchPermissionModificationsTypeDef,
45
+ NetworkInterfaceTypeDef,
45
46
  RouteTableTypeDef,
46
47
  SubnetTypeDef,
47
48
  TagTypeDef,
@@ -52,6 +53,10 @@ if TYPE_CHECKING:
52
53
  )
53
54
  from mypy_boto3_ecr import ECRClient
54
55
  from mypy_boto3_elb import ElasticLoadBalancingClient
56
+ from mypy_boto3_elb.type_defs import (
57
+ LoadBalancerDescriptionTypeDef,
58
+ TagDescriptionTypeDef,
59
+ )
55
60
  from mypy_boto3_iam import IAMClient, IAMServiceResource
56
61
  from mypy_boto3_iam.type_defs import AccessKeyMetadataTypeDef
57
62
  from mypy_boto3_logs import CloudWatchLogsClient
@@ -92,7 +97,9 @@ else:
92
97
  TransitGatewayTypeDef
93
98
  ) = TransitGatewayVpcAttachmentTypeDef = UpgradeTargetTypeDef = (
94
99
  VpcEndpointTypeDef
95
- ) = VpcTypeDef = object
100
+ ) = VpcTypeDef = NetworkInterfaceTypeDef = LoadBalancerDescriptionTypeDef = (
101
+ TagDescriptionTypeDef
102
+ ) = object
96
103
 
97
104
 
98
105
  class InvalidResourceTypeError(Exception):
@@ -194,9 +201,9 @@ class AWSApi:
194
201
  self.get_vpc_route_tables = lru_cache()(self.get_vpc_route_tables) # type: ignore[method-assign]
195
202
  self.get_vpc_subnets = lru_cache()(self.get_vpc_subnets) # type: ignore[method-assign]
196
203
  self._get_vpc_endpoints = lru_cache()(self._get_vpc_endpoints) # type: ignore[method-assign]
197
- self.get_alb_network_interface_ips = lru_cache()( # type: ignore[method-assign]
198
- self.get_alb_network_interface_ips
199
- )
204
+ self.get_network_interfaces = lru_cache()(self.get_network_interfaces) # type: ignore[method-assign]
205
+ self.get_load_balancers = lru_cache()(self.get_load_balancers) # type: ignore[method-assign]
206
+ self.get_load_balancer_tags = lru_cache()(self.get_load_balancer_tags) # type: ignore[method-assign]
200
207
 
201
208
  if init_users:
202
209
  self.init_users()
@@ -1568,8 +1575,9 @@ class AWSApi:
1568
1575
  ec2.create_tags(Resources=[resource_id], Tags=[tag_type_def])
1569
1576
 
1570
1577
  def get_alb_network_interface_ips(
1571
- self, assumed_role_data: tuple[str, str | None, str], service_name: str
1578
+ self, account: awsh.Account, service_name: str
1572
1579
  ) -> set[str]:
1580
+ assumed_role_data = self._get_account_assume_data(account)
1573
1581
  ec2_client = self._get_assumed_role_client(
1574
1582
  account_name=assumed_role_data[0],
1575
1583
  assume_role=assumed_role_data[1],
@@ -1583,14 +1591,14 @@ class AWSApi:
1583
1591
  client_type="elb",
1584
1592
  )
1585
1593
  service_tag = {"Key": "kubernetes.io/service-name", "Value": service_name}
1586
- nis = ec2_client.describe_network_interfaces()["NetworkInterfaces"]
1587
- lbs = elb_client.describe_load_balancers()["LoadBalancerDescriptions"]
1594
+ nis = self.get_network_interfaces(ec2_client)
1595
+ lbs = self.get_load_balancers(elb_client)
1588
1596
  result_ips = set()
1589
1597
  for lb in lbs:
1590
1598
  lb_name = lb["LoadBalancerName"]
1591
- tag_descriptions = elb_client.describe_tags(LoadBalancerNames=[lb_name])[
1592
- "TagDescriptions"
1593
- ]
1599
+ tag_descriptions = self.get_load_balancer_tags(
1600
+ elb=elb_client, lb_name=lb_name
1601
+ )
1594
1602
  for td in tag_descriptions:
1595
1603
  tags = td["Tags"]
1596
1604
  if service_tag not in tags:
@@ -1608,6 +1616,22 @@ class AWSApi:
1608
1616
 
1609
1617
  return result_ips
1610
1618
 
1619
+ @staticmethod
1620
+ def get_network_interfaces(ec2: EC2Client) -> list[NetworkInterfaceTypeDef]:
1621
+ return ec2.describe_network_interfaces()["NetworkInterfaces"]
1622
+
1623
+ @staticmethod
1624
+ def get_load_balancers(
1625
+ elb: ElasticLoadBalancingClient,
1626
+ ) -> list[LoadBalancerDescriptionTypeDef]:
1627
+ return elb.describe_load_balancers()["LoadBalancerDescriptions"]
1628
+
1629
+ @staticmethod
1630
+ def get_load_balancer_tags(
1631
+ elb: ElasticLoadBalancingClient, lb_name: str
1632
+ ) -> list[TagDescriptionTypeDef]:
1633
+ return elb.describe_tags(LoadBalancerNames=[lb_name])["TagDescriptions"]
1634
+
1611
1635
  @staticmethod
1612
1636
  def get_vpc_default_sg_id(vpc_id: str, ec2: EC2Client) -> str | None:
1613
1637
  vpc_security_groups = ec2.describe_security_groups(
@@ -0,0 +1,66 @@
1
+ import re
2
+ from datetime import UTC, datetime
3
+
4
+ import requests
5
+ from cryptography import x509
6
+ from cryptography.hazmat.primitives import hashes, serialization
7
+ from cryptography.hazmat.primitives.asymmetric import rsa
8
+ from cryptography.x509.oid import NameOID
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class RhcsV2Cert(BaseModel):
13
+ certificate: str
14
+ private_key: str
15
+ expiration_timestamp: int
16
+
17
+
18
+ def generate_cert(url: str, uid: str, pwd: str) -> RhcsV2Cert:
19
+ private_key = rsa.generate_private_key(65537, 4096)
20
+ csr = (
21
+ x509.CertificateSigningRequestBuilder()
22
+ .subject_name(
23
+ x509.Name([
24
+ x509.NameAttribute(NameOID.USER_ID, uid),
25
+ ])
26
+ )
27
+ .sign(private_key, hashes.SHA256())
28
+ )
29
+ data = {
30
+ "uid": uid,
31
+ "pwd": pwd,
32
+ "cert_request_type": "pkcs10",
33
+ "cert_request": csr.public_bytes(serialization.Encoding.PEM).decode(),
34
+ "profileId": "caDirAppUserCert",
35
+ "renewal": "false",
36
+ "xmlOutput": "false",
37
+ }
38
+ response = requests.post(url, data=data, verify=False)
39
+ response.raise_for_status()
40
+ cert_pem = re.search(
41
+ r'outputList\.outputVal="(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----(?:\\n|\r?\n)?)";',
42
+ response.text,
43
+ re.DOTALL,
44
+ )
45
+ if not cert_pem:
46
+ raise ValueError("Could not extract certificate PEM from response")
47
+ cert_expiry = re.search(r"Not\s+After:\s+(.*?UTC)(?:\\n|\r?\n)?", response.text)
48
+ if not cert_expiry:
49
+ raise ValueError("Could not extract expiration date from response")
50
+ # Weekday, Month Day, Year HH:MM:SS PM/AM Timezone
51
+ dt_expiry = datetime.strptime(cert_expiry.group(1), "%A, %B %d, %Y %I:%M:%S %p %Z")
52
+ dt_expiry = dt_expiry.replace(tzinfo=UTC)
53
+ private_key_pem = private_key.private_bytes(
54
+ encoding=serialization.Encoding.PEM,
55
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
56
+ encryption_algorithm=serialization.NoEncryption(),
57
+ ).decode()
58
+
59
+ return RhcsV2Cert(
60
+ private_key=private_key_pem,
61
+ certificate=cert_pem.group(1)
62
+ .encode()
63
+ .decode("unicode_escape")
64
+ .replace("\\/", "/"),
65
+ expiration_timestamp=int(dt_expiry.timestamp()),
66
+ )
@@ -5189,8 +5189,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5189
5189
  with AWSApi(
5190
5190
  1, [account], secret_reader=self.secret_reader, init_users=False
5191
5191
  ) as awsapi:
5192
- assumed_role_data = awsapi._get_account_assume_data(account)
5193
- ips = awsapi.get_alb_network_interface_ips(assumed_role_data, service_name)
5192
+ ips = awsapi.get_alb_network_interface_ips(account, service_name)
5194
5193
  if not ips:
5195
5194
  raise ValueError(
5196
5195
  f"[{account_name}/{identifier}] expected at least one "