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.
- {qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/RECORD +14 -8
- reconcile/cli.py +21 -0
- reconcile/gql_definitions/common/rhcs_provider_settings.py +68 -0
- reconcile/gql_definitions/introspection.json +165 -0
- reconcile/gql_definitions/rhcs/__init__.py +0 -0
- reconcile/gql_definitions/rhcs/certs.py +158 -0
- reconcile/openshift_rhcs_certs.py +269 -0
- reconcile/typed_queries/rhcs_provider_settings.py +21 -0
- reconcile/utils/aws_api.py +34 -10
- reconcile/utils/rhcsv2_certs.py +66 -0
- reconcile/utils/terrascript_aws_client.py +1 -2
- {qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
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
|
{qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/RECORD
RENAMED
@@ -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
|
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=
|
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=
|
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=
|
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.
|
813
|
-
qontract_reconcile-0.10.2.
|
814
|
-
qontract_reconcile-0.10.2.
|
815
|
-
qontract_reconcile-0.10.2.
|
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.")
|
reconcile/utils/aws_api.py
CHANGED
@@ -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 =
|
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.
|
198
|
-
|
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,
|
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 =
|
1587
|
-
lbs =
|
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 =
|
1592
|
-
|
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
|
-
|
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 "
|
{qontract_reconcile-0.10.2.dev202.dist-info → qontract_reconcile-0.10.2.dev204.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|