qontract-reconcile 0.10.1rc967__py3-none-any.whl → 0.10.1rc968__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.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/RECORD +13 -9
- reconcile/gql_definitions/fragments/serviceaccount_token.py +38 -0
- reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py +0 -0
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +132 -0
- reconcile/openshift_base.py +76 -0
- reconcile/openshift_serviceaccount_tokens.py +99 -63
- reconcile/queries.py +0 -73
- reconcile/test/test_openshift_base.py +62 -0
- reconcile/test/test_openshift_serviceaccount_tokens.py +228 -0
- {qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc968
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/RECORD
RENAMED
@@ -61,7 +61,7 @@ reconcile/ocm_groups.py,sha256=-rTPMewkdyo1De6gs4u-294p3z34oUbGfuNi8ov56Sk,3424
|
|
61
61
|
reconcile/ocm_machine_pools.py,sha256=poGfITOCJEMwYAJpiuL8SytgTcBmGIKEZPgNGld80TY,16563
|
62
62
|
reconcile/ocm_update_recommended_version.py,sha256=IYkfLXIprOW1jguZeELcGP1iBPuj-b53R-FTqKulMl8,4204
|
63
63
|
reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=aLgyInt9oIWAg0XtCiwJRUSwdPx3masKV8kHzkyEEOQ,4282
|
64
|
-
reconcile/openshift_base.py,sha256=
|
64
|
+
reconcile/openshift_base.py,sha256=IS5J8nccqRELRCheuz2wiLMHpaDypA9y3aKTMAjmMuE,52389
|
65
65
|
reconcile/openshift_cluster_bots.py,sha256=eRPYZqWMKFNxLlSN0QG97V5t1iIESQ0BbGaiaQP5VB0,10940
|
66
66
|
reconcile/openshift_clusterrolebindings.py,sha256=QfSy1Ik8eEY5XObc1Q4xyhqyErZenJmbPv_u9wcDNNo,5864
|
67
67
|
reconcile/openshift_groups.py,sha256=sK2wLWwNupztbfyFPl32VH42s_s8Mu3g-URdlisnwJc,9382
|
@@ -83,7 +83,7 @@ reconcile/openshift_saas_deploy_trigger_configs.py,sha256=eUejMGWuaQabZTLuvPLLvR
|
|
83
83
|
reconcile/openshift_saas_deploy_trigger_images.py,sha256=iUsiBGJf-CyFw7tSLWo59rXmSvsVnN6TTaAObbsVpNg,936
|
84
84
|
reconcile/openshift_saas_deploy_trigger_moving_commits.py,sha256=fpanSH-EGH15C9me--0VSpcpaw9BY4RTb8_mPtsSZGc,942
|
85
85
|
reconcile/openshift_saas_deploy_trigger_upstream_jobs.py,sha256=0CjfeVQE0QrRrOVuTxkXvBUdKNtYLYuX4mZRB48PQ9g,940
|
86
|
-
reconcile/openshift_serviceaccount_tokens.py,sha256=
|
86
|
+
reconcile/openshift_serviceaccount_tokens.py,sha256=9zNSMEHwcaGWy4H-OLkk0zLw80CYTBnLVEZKB_xq6Ew,7331
|
87
87
|
reconcile/openshift_tekton_resources.py,sha256=jKH5nw84aeYkgikxjQnjSOSF3m2kk3lp2BaPd3FfTew,16220
|
88
88
|
reconcile/openshift_upgrade_watcher.py,sha256=9IB321hlRZZhzdaR9G3zoWAhVv0-KzNiEqx73p3-wmk,6539
|
89
89
|
reconcile/openshift_users.py,sha256=63mar-swgidz8f10TCPJcofbMN9FETq-HuVFpi8dUL4,5293
|
@@ -94,7 +94,7 @@ reconcile/quay_mirror.py,sha256=sDLJUTWp8o9Swr2stQJl6PNR_5j-aUYyTtlKv7yznwQ,1482
|
|
94
94
|
reconcile/quay_mirror_org.py,sha256=utrJpJaKCs7U6WX6DODdfCeB0EmX-lUC8Y5fkmpgFSs,10764
|
95
95
|
reconcile/quay_permissions.py,sha256=9KOutS1w4RFQqkvMSy54VtsKNx56-phzP6yI_rEW-B8,4244
|
96
96
|
reconcile/quay_repos.py,sha256=cuEYG0HUe0ut5yvLdEwOF5-CmccpXQHRb_wDazvDrvQ,6895
|
97
|
-
reconcile/queries.py,sha256=
|
97
|
+
reconcile/queries.py,sha256=uUc_5ni7NEUCV4UwG96xxwp_lg-8_MaLRx69i9H--wQ,50164
|
98
98
|
reconcile/query_validator.py,sha256=BAjGrU8_VhzTOv5k0-uz0hY9ziZyconv8VAhgre1Auc,1497
|
99
99
|
reconcile/requests_sender.py,sha256=914iluuF4UVgG3VyxxtnHOu4yf6YKS2fIy6PViSsFTQ,3875
|
100
100
|
reconcile/resource_scraper.py,sha256=znXCHrU7YwPfKuxGBiUrV7T1tYtn4vlz9qmZlfy6Flg,2307
|
@@ -302,6 +302,7 @@ reconcile/gql_definitions/fragments/resource_limits_requirements.py,sha256=ucskQ
|
|
302
302
|
reconcile/gql_definitions/fragments/resource_requests_requirements.py,sha256=TFKO4YALFPanSvZvIJFz0dCioBU7i73Q6hkDtGMvs9I,736
|
303
303
|
reconcile/gql_definitions/fragments/resource_values.py,sha256=-N2lNRhWp8PgocmIeX3U9f3l90Q97N2lXoq1pXdb_LE,742
|
304
304
|
reconcile/gql_definitions/fragments/saas_target_namespace.py,sha256=8kMXeD7u2bmdjn10zHmMJ80ScOhUp6KqSfWfjWZW-40,4001
|
305
|
+
reconcile/gql_definitions/fragments/serviceaccount_token.py,sha256=2pG4rxAjvT-YsFBnm4zl301i7DCYznp99HOEGA-216I,1117
|
305
306
|
reconcile/gql_definitions/fragments/terraform_state.py,sha256=S5QuTR9YlvUObiU7hevS9ybxZEssWoRGqCR9YtGwePs,1024
|
306
307
|
reconcile/gql_definitions/fragments/upgrade_policy.py,sha256=cVza8zfra1E3yBsHiS-hKbys17fvv572GFnKshJjluE,1246
|
307
308
|
reconcile/gql_definitions/fragments/user.py,sha256=TZyFEs1fBg5PkvWdyCxFDZ_3aRhcQzusfhObXFiOU_0,1025
|
@@ -341,6 +342,8 @@ reconcile/gql_definitions/openshift_cluster_bots/clusters.py,sha256=QshhCQeFRu_o
|
|
341
342
|
reconcile/gql_definitions/openshift_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
342
343
|
reconcile/gql_definitions/openshift_groups/managed_groups.py,sha256=mBWZX9xxeW3eB1ylnAI5x_7UBacRqJf_H6um-fB_nKc,2013
|
343
344
|
reconcile/gql_definitions/openshift_groups/managed_roles.py,sha256=rsHMgDWwnh0RRJpS_-TxD22KQPC6_7rtRfauTOtxH5I,2627
|
345
|
+
reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
346
|
+
reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py,sha256=FeraeQThHl2UbhTuCPDwm3ltczF_jfZn5E3Squ8-znw,3313
|
344
347
|
reconcile/gql_definitions/quay_membership/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
345
348
|
reconcile/gql_definitions/quay_membership/quay_membership.py,sha256=H2xHvdNr3K0QzB2dituwStUIWCqePt35dkgeUZycECM,2824
|
346
349
|
reconcile/gql_definitions/rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -518,7 +521,7 @@ reconcile/test/test_ocm_clusters_manifest_updates.py,sha256=jFRVfc5jby1kI2x_gT6w
|
|
518
521
|
reconcile/test/test_ocm_machine_pools.py,sha256=_1tG-nF8edU29ONDUGXtR3J2-E8FWv-PI0Y9gb8HQfY,29697
|
519
522
|
reconcile/test/test_ocm_update_recommended_version.py,sha256=iA4BVirTGVXlwcOyeR52IuNO81X_8NR6ZNd7ZFE7igs,4328
|
520
523
|
reconcile/test/test_ocm_upgrade_scheduler_org_updater.py,sha256=V91g2XQMa2nvKwkLVWkiPwNL6pMQE16s4jO0oXJ6wdk,4330
|
521
|
-
reconcile/test/test_openshift_base.py,sha256=
|
524
|
+
reconcile/test/test_openshift_base.py,sha256=SX8URKF8Kn21nA-qcmICSUaZKHuc5YTgPzfcb2rHvk8,36385
|
522
525
|
reconcile/test/test_openshift_cluster_bots.py,sha256=sSGLgxnXO82xcfTFVNJzsrDuNfObwAR_-AkDe4B_4WE,7983
|
523
526
|
reconcile/test/test_openshift_namespace_labels.py,sha256=i4S5QJFxMRjLkwi3iO6A-uhjgZ1QZb4jYXwB696m82Y,12070
|
524
527
|
reconcile/test/test_openshift_namespaces.py,sha256=HmRnCE5EnFt3MYceVEFHmk8wWRtCrxu2AFGFkY9pdyA,9214
|
@@ -527,6 +530,7 @@ reconcile/test/test_openshift_resources_base.py,sha256=LtlR9x3o7KkSEw0JN0fZhinFe
|
|
527
530
|
reconcile/test/test_openshift_saas_deploy.py,sha256=3QXMrN9dXIiR0JktVDNQ7yJSexMTjZLb1tbRrB3-7uU,5991
|
528
531
|
reconcile/test/test_openshift_saas_deploy_change_tester.py,sha256=1yVe54Hx9YdVjn6qdnKge5Sa_s732c-8uZqCnuT1gGI,12871
|
529
532
|
reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py,sha256=UQx1iJ21rsMa2whG-rtUIuTXbUzc0Ngr7jRLKXZCCCI,2838
|
533
|
+
reconcile/test/test_openshift_serviceaccount_tokens.py,sha256=btaI8Au5XdZXXHtQfFPA8bGSTrXReC8ywJKUjaIQc-A,7341
|
530
534
|
reconcile/test/test_openshift_tekton_resources.py,sha256=RtRWsdm51S13OSkENC9nY_rOH0QELSCaO5tjF0XqIDI,11222
|
531
535
|
reconcile/test/test_openshift_upgrade_watcher.py,sha256=0GDQ_YFHIX8DbkbDYSuLv9uZeeg4NwP1vlOqvSaZvN4,7183
|
532
536
|
reconcile/test/test_prometheus_rules_tester.py,sha256=cgVkPM3KcAw69bOkJ6iR2Lfog_WgblyoqVRtXv4ly7o,5685
|
@@ -847,8 +851,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
|
|
847
851
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
848
852
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
849
853
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
850
|
-
qontract_reconcile-0.10.
|
851
|
-
qontract_reconcile-0.10.
|
852
|
-
qontract_reconcile-0.10.
|
853
|
-
qontract_reconcile-0.10.
|
854
|
-
qontract_reconcile-0.10.
|
854
|
+
qontract_reconcile-0.10.1rc968.dist-info/METADATA,sha256=9_cctR46UDeQ8KvizoJx12uh1u7_W-xYEprAgQpq_DQ,2262
|
855
|
+
qontract_reconcile-0.10.1rc968.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
856
|
+
qontract_reconcile-0.10.1rc968.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
857
|
+
qontract_reconcile-0.10.1rc968.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
858
|
+
qontract_reconcile-0.10.1rc968.dist-info/RECORD,,
|
@@ -0,0 +1,38 @@
|
|
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.oc_connection_cluster import OcConnectionCluster
|
21
|
+
|
22
|
+
|
23
|
+
class ConfiguredBaseModel(BaseModel):
|
24
|
+
class Config:
|
25
|
+
smart_union=True
|
26
|
+
extra=Extra.forbid
|
27
|
+
|
28
|
+
|
29
|
+
class NamespaceV1(ConfiguredBaseModel):
|
30
|
+
name: str = Field(..., alias="name")
|
31
|
+
delete: Optional[bool] = Field(..., alias="delete")
|
32
|
+
cluster: OcConnectionCluster = Field(..., alias="cluster")
|
33
|
+
|
34
|
+
|
35
|
+
class ServiceAccountToken(ConfiguredBaseModel):
|
36
|
+
name: Optional[str] = Field(..., alias="name")
|
37
|
+
service_account_name: str = Field(..., alias="serviceAccountName")
|
38
|
+
namespace: NamespaceV1 = Field(..., alias="namespace")
|
File without changes
|
@@ -0,0 +1,132 @@
|
|
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.oc_connection_cluster import OcConnectionCluster
|
21
|
+
from reconcile.gql_definitions.fragments.serviceaccount_token import ServiceAccountToken
|
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 OcConnectionCluster on Cluster_v1 {
|
37
|
+
name
|
38
|
+
serverUrl
|
39
|
+
internal
|
40
|
+
insecureSkipTLSVerify
|
41
|
+
jumpHost {
|
42
|
+
...CommonJumphostFields
|
43
|
+
}
|
44
|
+
automationToken {
|
45
|
+
...VaultSecret
|
46
|
+
}
|
47
|
+
clusterAdminAutomationToken {
|
48
|
+
...VaultSecret
|
49
|
+
}
|
50
|
+
disable {
|
51
|
+
integrations
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
fragment ServiceAccountToken on ServiceAccountTokenSpec_v1 {
|
56
|
+
name
|
57
|
+
serviceAccountName
|
58
|
+
namespace {
|
59
|
+
name
|
60
|
+
delete
|
61
|
+
cluster {
|
62
|
+
...OcConnectionCluster
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
fragment VaultSecret on VaultSecret_v1 {
|
68
|
+
path
|
69
|
+
field
|
70
|
+
version
|
71
|
+
format
|
72
|
+
}
|
73
|
+
|
74
|
+
query ServiceAccountTokens {
|
75
|
+
namespaces: namespaces_v1 {
|
76
|
+
name
|
77
|
+
delete
|
78
|
+
cluster {
|
79
|
+
...OcConnectionCluster
|
80
|
+
}
|
81
|
+
sharedResources {
|
82
|
+
openshiftServiceAccountTokens {
|
83
|
+
...ServiceAccountToken
|
84
|
+
}
|
85
|
+
}
|
86
|
+
openshiftServiceAccountTokens {
|
87
|
+
...ServiceAccountToken
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
"""
|
92
|
+
|
93
|
+
|
94
|
+
class ConfiguredBaseModel(BaseModel):
|
95
|
+
class Config:
|
96
|
+
smart_union=True
|
97
|
+
extra=Extra.forbid
|
98
|
+
|
99
|
+
|
100
|
+
class SharedResourcesV1(ConfiguredBaseModel):
|
101
|
+
openshift_service_account_tokens: Optional[list[ServiceAccountToken]] = Field(..., alias="openshiftServiceAccountTokens")
|
102
|
+
|
103
|
+
|
104
|
+
class NamespaceV1(ConfiguredBaseModel):
|
105
|
+
name: str = Field(..., alias="name")
|
106
|
+
delete: Optional[bool] = Field(..., alias="delete")
|
107
|
+
cluster: OcConnectionCluster = Field(..., alias="cluster")
|
108
|
+
shared_resources: Optional[list[SharedResourcesV1]] = Field(..., alias="sharedResources")
|
109
|
+
openshift_service_account_tokens: Optional[list[ServiceAccountToken]] = Field(..., alias="openshiftServiceAccountTokens")
|
110
|
+
|
111
|
+
|
112
|
+
class ServiceAccountTokensQueryData(ConfiguredBaseModel):
|
113
|
+
namespaces: Optional[list[NamespaceV1]] = Field(..., alias="namespaces")
|
114
|
+
|
115
|
+
|
116
|
+
def query(query_func: Callable, **kwargs: Any) -> ServiceAccountTokensQueryData:
|
117
|
+
"""
|
118
|
+
This is a convenience function which queries and parses the data into
|
119
|
+
concrete types. It should be compatible with most GQL clients.
|
120
|
+
You do not have to use it to consume the generated data classes.
|
121
|
+
Alternatively, you can also mime and alternate the behavior
|
122
|
+
of this function in the caller.
|
123
|
+
|
124
|
+
Parameters:
|
125
|
+
query_func (Callable): Function which queries your GQL Server
|
126
|
+
kwargs: optional arguments that will be passed to the query function
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
ServiceAccountTokensQueryData: queried data parsed into generated classes
|
130
|
+
"""
|
131
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
132
|
+
return ServiceAccountTokensQueryData(**raw_data)
|
reconcile/openshift_base.py
CHANGED
@@ -1306,6 +1306,82 @@ def aggregate_shared_resources(namespace_info, shared_resources_type):
|
|
1306
1306
|
namespace_info[shared_resources_type] = namespace_type_resources
|
1307
1307
|
|
1308
1308
|
|
1309
|
+
@runtime_checkable
|
1310
|
+
class HasOpenShiftResources(Protocol):
|
1311
|
+
openshift_resources: list | None
|
1312
|
+
|
1313
|
+
|
1314
|
+
@runtime_checkable
|
1315
|
+
class HasOpenshiftServiceAccountTokens(Protocol):
|
1316
|
+
openshift_service_account_tokens: list | None
|
1317
|
+
|
1318
|
+
|
1319
|
+
@runtime_checkable
|
1320
|
+
class HasSharedResourcesOpenShiftResources(Protocol):
|
1321
|
+
@property
|
1322
|
+
def shared_resources(self) -> Sequence[HasOpenShiftResources] | None:
|
1323
|
+
pass
|
1324
|
+
|
1325
|
+
|
1326
|
+
@runtime_checkable
|
1327
|
+
class HasSharedResourcesOpenshiftServiceAccountTokens(Protocol):
|
1328
|
+
@property
|
1329
|
+
def shared_resources(self) -> Sequence[HasOpenshiftServiceAccountTokens] | None:
|
1330
|
+
pass
|
1331
|
+
|
1332
|
+
|
1333
|
+
@runtime_checkable
|
1334
|
+
class HasOpenshiftServiceAccountTokensAndSharedResources(
|
1335
|
+
HasOpenshiftServiceAccountTokens,
|
1336
|
+
HasSharedResourcesOpenshiftServiceAccountTokens,
|
1337
|
+
Protocol,
|
1338
|
+
): ...
|
1339
|
+
|
1340
|
+
|
1341
|
+
@runtime_checkable
|
1342
|
+
class HasOpenShiftResourcesAndSharedResources(
|
1343
|
+
HasOpenShiftResources, HasSharedResourcesOpenShiftResources, Protocol
|
1344
|
+
): ...
|
1345
|
+
|
1346
|
+
|
1347
|
+
def aggregate_shared_resources_typed(
|
1348
|
+
namespace: HasOpenshiftServiceAccountTokensAndSharedResources
|
1349
|
+
| HasOpenShiftResourcesAndSharedResources,
|
1350
|
+
) -> None:
|
1351
|
+
"""This function aggregates the shared resources to the appropriate namespace section.
|
1352
|
+
|
1353
|
+
Attention: It updates the namespace object in place and isn't indempotent!
|
1354
|
+
"""
|
1355
|
+
if not namespace.shared_resources:
|
1356
|
+
return
|
1357
|
+
|
1358
|
+
match namespace:
|
1359
|
+
case HasOpenshiftServiceAccountTokensAndSharedResources():
|
1360
|
+
shared_type_resources_items = []
|
1361
|
+
for ost_shared_resources_item in namespace.shared_resources:
|
1362
|
+
if shared_type_resources := getattr(
|
1363
|
+
ost_shared_resources_item, "openshift_service_account_tokens"
|
1364
|
+
):
|
1365
|
+
shared_type_resources_items.extend(shared_type_resources)
|
1366
|
+
if namespace.openshift_service_account_tokens:
|
1367
|
+
namespace.openshift_service_account_tokens.extend(
|
1368
|
+
shared_type_resources_items
|
1369
|
+
)
|
1370
|
+
else:
|
1371
|
+
namespace.openshift_service_account_tokens = shared_type_resources_items
|
1372
|
+
case HasOpenShiftResourcesAndSharedResources():
|
1373
|
+
shared_type_resources_items = []
|
1374
|
+
for or_shared_resources_item in namespace.shared_resources:
|
1375
|
+
if shared_type_resources := getattr(
|
1376
|
+
or_shared_resources_item, "openshift_resources"
|
1377
|
+
):
|
1378
|
+
shared_type_resources_items.extend(shared_type_resources)
|
1379
|
+
if namespace.openshift_resources:
|
1380
|
+
namespace.openshift_resources.extend(shared_type_resources_items)
|
1381
|
+
else:
|
1382
|
+
namespace.openshift_resources = shared_type_resources_items
|
1383
|
+
|
1384
|
+
|
1309
1385
|
def determine_user_keys_for_access(
|
1310
1386
|
cluster_name: str,
|
1311
1387
|
auth_list: Sequence[dict[str, str] | HasService],
|
@@ -1,9 +1,15 @@
|
|
1
1
|
import logging
|
2
2
|
import sys
|
3
|
+
from collections.abc import Callable, Iterable
|
3
4
|
|
4
5
|
import reconcile.openshift_base as ob
|
5
|
-
from reconcile import
|
6
|
+
from reconcile.gql_definitions.openshift_serviceaccount_tokens.tokens import NamespaceV1
|
7
|
+
from reconcile.gql_definitions.openshift_serviceaccount_tokens.tokens import (
|
8
|
+
query as serviceaccount_tokens_query,
|
9
|
+
)
|
10
|
+
from reconcile.utils import gql
|
6
11
|
from reconcile.utils.defer import defer
|
12
|
+
from reconcile.utils.disabled_integrations import integration_is_enabled
|
7
13
|
from reconcile.utils.oc import OC_Map
|
8
14
|
from reconcile.utils.openshift_resource import OpenshiftResource as OR
|
9
15
|
from reconcile.utils.openshift_resource import ResourceInventory
|
@@ -14,7 +20,7 @@ QONTRACT_INTEGRATION = "openshift-serviceaccount-tokens"
|
|
14
20
|
QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
|
15
21
|
|
16
22
|
|
17
|
-
def construct_sa_token_oc_resource(name, sa_token):
|
23
|
+
def construct_sa_token_oc_resource(name: str, sa_token: str) -> OR:
|
18
24
|
body = {
|
19
25
|
"apiVersion": "v1",
|
20
26
|
"kind": "Secret",
|
@@ -47,104 +53,133 @@ def get_tokens_for_service_account(
|
|
47
53
|
return result
|
48
54
|
|
49
55
|
|
50
|
-
def fetch_desired_state(
|
51
|
-
|
52
|
-
|
56
|
+
def fetch_desired_state(
|
57
|
+
namespaces: list[NamespaceV1], ri: ResourceInventory, oc_map: OC_Map
|
58
|
+
) -> None:
|
59
|
+
for namespace in namespaces:
|
60
|
+
if not namespace.openshift_service_account_tokens:
|
53
61
|
continue
|
54
|
-
|
55
|
-
|
56
|
-
oc = oc_map.get(cluster_name)
|
57
|
-
if not oc:
|
62
|
+
|
63
|
+
if not (oc := oc_map.get(namespace.cluster.name)):
|
58
64
|
logging.log(level=oc.log_level, msg=oc.message)
|
59
65
|
continue
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
sa_namespace_name = sa_namespace_info["name"]
|
64
|
-
sa_cluster_name = sa_namespace_info["cluster"]["name"]
|
65
|
-
oc = oc_map.get(sa_cluster_name)
|
66
|
+
|
67
|
+
for sat in namespace.openshift_service_account_tokens:
|
68
|
+
oc = oc_map.get(sat.namespace.cluster.name)
|
66
69
|
if not oc:
|
67
70
|
if oc.log_level >= logging.ERROR:
|
68
71
|
ri.register_error()
|
69
72
|
logging.log(level=oc.log_level, msg=oc.message)
|
70
73
|
continue
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
sat.get("name") or f"{sa_cluster_name}-{sa_namespace_name}-{sa_name}"
|
75
|
+
namespace_secrets = oc.get_items(
|
76
|
+
kind="Secret", namespace=sat.namespace.name
|
75
77
|
)
|
76
78
|
try:
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
sa_tokens = get_tokens_for_service_account(
|
80
|
+
sat.service_account_name, namespace_secrets
|
81
|
+
)
|
82
|
+
sa_tokens.sort(key=lambda t: t["metadata"]["name"])
|
83
|
+
# take the first token found
|
84
|
+
sa_token = sa_tokens[0]["data"]["token"]
|
80
85
|
except KeyError:
|
81
|
-
logging.
|
82
|
-
|
83
|
-
msg=f"[{sa_cluster_name}/{sa_namespace_name}] Token not found for service account: {sa_name}",
|
86
|
+
logging.error(
|
87
|
+
f"[{sat.namespace.cluster.name}/{sat.namespace.name}] Token not found for service account: {sat.service_account_name}"
|
84
88
|
)
|
85
89
|
raise
|
86
90
|
except IndexError:
|
87
|
-
logging.
|
88
|
-
|
89
|
-
msg=f"[{sa_cluster_name}/{sa_namespace_name}] 0 Secret found for service account: {sa_name}",
|
91
|
+
logging.error(
|
92
|
+
f"[{sat.namespace.cluster.name}/{sat.namespace.name}] 0 Secret found for service account: {sat.service_account_name}"
|
90
93
|
)
|
91
94
|
raise
|
92
95
|
|
93
|
-
oc_resource = construct_sa_token_oc_resource(
|
94
|
-
|
95
|
-
|
96
|
+
oc_resource = construct_sa_token_oc_resource(
|
97
|
+
name=(
|
98
|
+
sat.name
|
99
|
+
or f"{sat.namespace.cluster.name}-{sat.namespace.name}-{sat.service_account_name}"
|
100
|
+
),
|
101
|
+
sa_token=sa_token,
|
102
|
+
)
|
103
|
+
ri.add_desired_resource(
|
104
|
+
namespace.cluster.name,
|
105
|
+
namespace.name,
|
106
|
+
oc_resource,
|
96
107
|
)
|
97
108
|
|
98
109
|
|
99
|
-
def write_outputs_to_vault(
|
110
|
+
def write_outputs_to_vault(
|
111
|
+
vault_client: VaultClient, vault_path: str, ri: ResourceInventory
|
112
|
+
) -> None:
|
100
113
|
integration_name = QONTRACT_INTEGRATION.replace("_", "-")
|
101
|
-
vault_client = VaultClient()
|
102
114
|
for cluster, namespace, _, data in ri:
|
103
115
|
for name, d_item in data["desired"].items():
|
104
116
|
body_data = d_item.body["data"]
|
105
117
|
# write secret to per-namespace location
|
106
118
|
secret_path = (
|
107
|
-
f"{vault_path}/{integration_name}/
|
119
|
+
f"{vault_path}/{integration_name}/{cluster}/{namespace}/{name}"
|
108
120
|
)
|
109
121
|
secret = {"path": secret_path, "data": body_data}
|
110
|
-
vault_client.write(secret)
|
122
|
+
vault_client.write(secret) # type: ignore
|
111
123
|
# write secret to shared-resources location
|
112
|
-
secret_path =
|
113
|
-
f"{vault_path}/{integration_name}/" + f"shared-resources/{name}"
|
114
|
-
)
|
124
|
+
secret_path = f"{vault_path}/{integration_name}/shared-resources/{name}"
|
115
125
|
secret = {"path": secret_path, "data": body_data}
|
116
|
-
vault_client.write(secret)
|
126
|
+
vault_client.write(secret) # type: ignore
|
117
127
|
|
118
128
|
|
119
|
-
def canonicalize_namespaces(namespaces):
|
120
|
-
canonicalized_namespaces =
|
121
|
-
for
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
129
|
+
def canonicalize_namespaces(namespaces: Iterable[NamespaceV1]) -> list[NamespaceV1]:
|
130
|
+
canonicalized_namespaces: dict[str, NamespaceV1] = {}
|
131
|
+
for namespace in namespaces:
|
132
|
+
ob.aggregate_shared_resources_typed(namespace)
|
133
|
+
if namespace.openshift_service_account_tokens:
|
134
|
+
canonicalized_namespaces[f"{namespace.cluster.name}/{namespace.name}"] = (
|
135
|
+
namespace
|
136
|
+
)
|
137
|
+
for namespace in list(canonicalized_namespaces.values()):
|
138
|
+
for sat in namespace.openshift_service_account_tokens or []:
|
139
|
+
key = f"{sat.namespace.cluster.name}/{sat.namespace.name}"
|
140
|
+
if key not in canonicalized_namespaces:
|
141
|
+
canonicalized_namespaces[key] = NamespaceV1(
|
142
|
+
**sat.namespace.dict(by_alias=True),
|
143
|
+
sharedResources=None,
|
144
|
+
openshiftServiceAccountTokens=None,
|
145
|
+
)
|
146
|
+
return list(canonicalized_namespaces.values())
|
147
|
+
|
148
|
+
|
149
|
+
def get_namespaces_with_serviceaccount_tokens(
|
150
|
+
query_func: Callable,
|
151
|
+
) -> list[NamespaceV1]:
|
152
|
+
return [
|
153
|
+
namespace
|
154
|
+
for namespace in serviceaccount_tokens_query(query_func=query_func).namespaces
|
155
|
+
or []
|
156
|
+
if integration_is_enabled(QONTRACT_INTEGRATION, namespace.cluster)
|
157
|
+
and not bool(namespace.delete)
|
158
|
+
and (
|
159
|
+
namespace.openshift_service_account_tokens
|
160
|
+
or any(
|
161
|
+
sr.openshift_service_account_tokens
|
162
|
+
for sr in namespace.shared_resources or []
|
163
|
+
)
|
127
164
|
)
|
128
|
-
|
129
|
-
canonicalized_namespaces.append(namespace_info)
|
130
|
-
for sat in openshift_serviceaccount_tokens:
|
131
|
-
canonicalized_namespaces.append(sat["namespace"])
|
132
|
-
|
133
|
-
return canonicalized_namespaces
|
165
|
+
]
|
134
166
|
|
135
167
|
|
136
168
|
@defer
|
137
169
|
def run(
|
138
|
-
dry_run,
|
139
|
-
thread_pool_size=10,
|
140
|
-
internal=None,
|
141
|
-
use_jump_host=True,
|
142
|
-
vault_output_path="",
|
143
|
-
defer=None,
|
144
|
-
):
|
145
|
-
|
170
|
+
dry_run: bool,
|
171
|
+
thread_pool_size: int = 10,
|
172
|
+
internal: bool | None = None,
|
173
|
+
use_jump_host: bool = True,
|
174
|
+
vault_output_path: str = "",
|
175
|
+
defer: Callable | None = None,
|
176
|
+
) -> None:
|
177
|
+
gql_api = gql.get_api()
|
178
|
+
namespaces = canonicalize_namespaces(
|
179
|
+
get_namespaces_with_serviceaccount_tokens(gql_api.query)
|
180
|
+
)
|
146
181
|
ri, oc_map = ob.fetch_current_state(
|
147
|
-
namespaces=namespaces,
|
182
|
+
namespaces=[ns.dict(by_alias=True) for ns in namespaces],
|
148
183
|
thread_pool_size=thread_pool_size,
|
149
184
|
integration=QONTRACT_INTEGRATION,
|
150
185
|
integration_version=QONTRACT_INTEGRATION_VERSION,
|
@@ -152,12 +187,13 @@ def run(
|
|
152
187
|
internal=internal,
|
153
188
|
use_jump_host=use_jump_host,
|
154
189
|
)
|
155
|
-
defer
|
190
|
+
if defer:
|
191
|
+
defer(oc_map.cleanup)
|
156
192
|
fetch_desired_state(namespaces, ri, oc_map)
|
157
193
|
ob.publish_metrics(ri, QONTRACT_INTEGRATION)
|
158
194
|
ob.realize_data(dry_run, oc_map, ri, thread_pool_size)
|
159
195
|
if not dry_run and vault_output_path:
|
160
|
-
write_outputs_to_vault(vault_output_path, ri)
|
196
|
+
write_outputs_to_vault(VaultClient(), vault_output_path, ri)
|
161
197
|
|
162
198
|
if ri.has_error_registered():
|
163
199
|
sys.exit(1)
|
reconcile/queries.py
CHANGED
@@ -1553,79 +1553,6 @@ def get_namespaces(minimal=False):
|
|
1553
1553
|
return gqlapi.query(NAMESPACES_QUERY)["namespaces"]
|
1554
1554
|
|
1555
1555
|
|
1556
|
-
SA_TOKEN = """
|
1557
|
-
name
|
1558
|
-
namespace {
|
1559
|
-
name
|
1560
|
-
cluster {
|
1561
|
-
name
|
1562
|
-
serverUrl
|
1563
|
-
insecureSkipTLSVerify
|
1564
|
-
jumpHost {
|
1565
|
-
%s
|
1566
|
-
}
|
1567
|
-
automationToken {
|
1568
|
-
path
|
1569
|
-
field
|
1570
|
-
version
|
1571
|
-
format
|
1572
|
-
}
|
1573
|
-
internal
|
1574
|
-
disable {
|
1575
|
-
integrations
|
1576
|
-
}
|
1577
|
-
}
|
1578
|
-
}
|
1579
|
-
serviceAccountName
|
1580
|
-
""" % (indent(JUMPHOST_FIELDS, 6 * " "),)
|
1581
|
-
|
1582
|
-
|
1583
|
-
SERVICEACCOUNT_TOKENS_QUERY = """
|
1584
|
-
{
|
1585
|
-
namespaces: namespaces_v1 {
|
1586
|
-
name
|
1587
|
-
delete
|
1588
|
-
cluster {
|
1589
|
-
name
|
1590
|
-
serverUrl
|
1591
|
-
insecureSkipTLSVerify
|
1592
|
-
jumpHost {
|
1593
|
-
%s
|
1594
|
-
}
|
1595
|
-
automationToken {
|
1596
|
-
path
|
1597
|
-
field
|
1598
|
-
version
|
1599
|
-
format
|
1600
|
-
}
|
1601
|
-
internal
|
1602
|
-
disable {
|
1603
|
-
integrations
|
1604
|
-
}
|
1605
|
-
}
|
1606
|
-
sharedResources {
|
1607
|
-
openshiftServiceAccountTokens {
|
1608
|
-
%s
|
1609
|
-
}
|
1610
|
-
}
|
1611
|
-
openshiftServiceAccountTokens {
|
1612
|
-
%s
|
1613
|
-
}
|
1614
|
-
}
|
1615
|
-
}
|
1616
|
-
""" % (
|
1617
|
-
indent(JUMPHOST_FIELDS, 8 * " "),
|
1618
|
-
indent(SA_TOKEN, 8 * " "),
|
1619
|
-
indent(SA_TOKEN, 6 * " "),
|
1620
|
-
)
|
1621
|
-
|
1622
|
-
|
1623
|
-
def get_serviceaccount_tokens():
|
1624
|
-
"""Returns all namespaces with ServiceAccount tokens information"""
|
1625
|
-
gqlapi = gql.get_api()
|
1626
|
-
return gqlapi.query(SERVICEACCOUNT_TOKENS_QUERY)["namespaces"]
|
1627
|
-
|
1628
|
-
|
1629
1556
|
PRODUCTS_QUERY = """
|
1630
1557
|
{
|
1631
1558
|
products: products_v1 {
|
@@ -1205,3 +1205,65 @@ def test_get_state_count_combinations():
|
|
1205
1205
|
]
|
1206
1206
|
expected = {"c1": 2, "c2": 2, "c3": 1}
|
1207
1207
|
assert expected == sut.get_state_count_combinations(state)
|
1208
|
+
|
1209
|
+
|
1210
|
+
def test_aggregate_shared_resources_typed_openshift_service_resources() -> None:
|
1211
|
+
class OpenShiftResourcesStub(BaseModel):
|
1212
|
+
openshift_resources: list | None
|
1213
|
+
|
1214
|
+
class OpenShiftResourcesAndSharedResourcesStub(OpenShiftResourcesStub, BaseModel):
|
1215
|
+
shared_resources: list[OpenShiftResourcesStub] | None
|
1216
|
+
|
1217
|
+
namespace = OpenShiftResourcesAndSharedResourcesStub(
|
1218
|
+
openshift_resources=[1], shared_resources=None
|
1219
|
+
)
|
1220
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1221
|
+
assert namespace.openshift_resources == [1]
|
1222
|
+
|
1223
|
+
namespace = OpenShiftResourcesAndSharedResourcesStub(
|
1224
|
+
openshift_resources=None,
|
1225
|
+
shared_resources=[OpenShiftResourcesStub(openshift_resources=[2])],
|
1226
|
+
)
|
1227
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1228
|
+
assert namespace.openshift_resources == [2]
|
1229
|
+
|
1230
|
+
namespace = OpenShiftResourcesAndSharedResourcesStub(
|
1231
|
+
openshift_resources=[1],
|
1232
|
+
shared_resources=[OpenShiftResourcesStub(openshift_resources=[2])],
|
1233
|
+
)
|
1234
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1235
|
+
assert namespace.openshift_resources == [1, 2]
|
1236
|
+
|
1237
|
+
|
1238
|
+
def test_aggregate_shared_resources_typed_openshift_service_account_token() -> None:
|
1239
|
+
class OpenshiftServiceAccountTokensStub(BaseModel):
|
1240
|
+
openshift_service_account_tokens: list | None
|
1241
|
+
|
1242
|
+
class OpenshiftServiceAccountTokensAndSharedResourcesStub(
|
1243
|
+
OpenshiftServiceAccountTokensStub, BaseModel
|
1244
|
+
):
|
1245
|
+
shared_resources: list[OpenshiftServiceAccountTokensStub] | None
|
1246
|
+
|
1247
|
+
namespace = OpenshiftServiceAccountTokensAndSharedResourcesStub(
|
1248
|
+
openshift_service_account_tokens=[1], shared_resources=None
|
1249
|
+
)
|
1250
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1251
|
+
assert namespace.openshift_service_account_tokens == [1]
|
1252
|
+
|
1253
|
+
namespace = OpenshiftServiceAccountTokensAndSharedResourcesStub(
|
1254
|
+
openshift_service_account_tokens=None,
|
1255
|
+
shared_resources=[
|
1256
|
+
OpenshiftServiceAccountTokensStub(openshift_service_account_tokens=[2])
|
1257
|
+
],
|
1258
|
+
)
|
1259
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1260
|
+
assert namespace.openshift_service_account_tokens == [2]
|
1261
|
+
|
1262
|
+
namespace = OpenshiftServiceAccountTokensAndSharedResourcesStub(
|
1263
|
+
openshift_service_account_tokens=[1],
|
1264
|
+
shared_resources=[
|
1265
|
+
OpenshiftServiceAccountTokensStub(openshift_service_account_tokens=[2])
|
1266
|
+
],
|
1267
|
+
)
|
1268
|
+
sut.aggregate_shared_resources_typed(namespace=namespace)
|
1269
|
+
assert namespace.openshift_service_account_tokens == [1, 2]
|
@@ -0,0 +1,228 @@
|
|
1
|
+
from collections.abc import Callable, Mapping
|
2
|
+
from typing import Any
|
3
|
+
from unittest.mock import call
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from pytest_mock import MockerFixture
|
7
|
+
|
8
|
+
from reconcile.gql_definitions.openshift_serviceaccount_tokens.tokens import NamespaceV1
|
9
|
+
from reconcile.openshift_serviceaccount_tokens import (
|
10
|
+
canonicalize_namespaces,
|
11
|
+
construct_sa_token_oc_resource,
|
12
|
+
fetch_desired_state,
|
13
|
+
get_namespaces_with_serviceaccount_tokens,
|
14
|
+
get_tokens_for_service_account,
|
15
|
+
write_outputs_to_vault,
|
16
|
+
)
|
17
|
+
from reconcile.test.fixtures import Fixtures
|
18
|
+
from reconcile.utils.oc import OC_Map, OCCli
|
19
|
+
from reconcile.utils.openshift_resource import ResourceInventory
|
20
|
+
from reconcile.utils.vault import _VaultClient
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.fixture
|
24
|
+
def fx() -> Fixtures:
|
25
|
+
return Fixtures("openshift_serviceaccount_tokens")
|
26
|
+
|
27
|
+
|
28
|
+
@pytest.fixture
|
29
|
+
def query_func(
|
30
|
+
data_factory: Callable[[type[NamespaceV1], Mapping[str, Any]], Mapping[str, Any]],
|
31
|
+
fx: Fixtures,
|
32
|
+
) -> Callable:
|
33
|
+
def q(*args: Any, **kwargs: Any) -> dict:
|
34
|
+
return {
|
35
|
+
"namespaces": [
|
36
|
+
data_factory(NamespaceV1, item)
|
37
|
+
for item in fx.get_anymarkup("namespaces.yml")["namespaces"]
|
38
|
+
]
|
39
|
+
}
|
40
|
+
|
41
|
+
return q
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.fixture
|
45
|
+
def namespaces(query_func: Callable) -> list[NamespaceV1]:
|
46
|
+
return get_namespaces_with_serviceaccount_tokens(query_func)
|
47
|
+
|
48
|
+
|
49
|
+
@pytest.fixture
|
50
|
+
def ri(namespaces: list[NamespaceV1]) -> ResourceInventory:
|
51
|
+
_ri = ResourceInventory()
|
52
|
+
_ri.initialize_resource_type(
|
53
|
+
cluster="cluster", namespace="namespace", resource_type="Secret"
|
54
|
+
)
|
55
|
+
for ns in namespaces:
|
56
|
+
_ri.initialize_resource_type(
|
57
|
+
cluster=ns.cluster.name, namespace=ns.name, resource_type="Secret"
|
58
|
+
)
|
59
|
+
return _ri
|
60
|
+
|
61
|
+
|
62
|
+
def test_openshift_serviceaccount_tokens__construct_sa_token_oc_resource() -> None:
|
63
|
+
qr = construct_sa_token_oc_resource("foobar", "token")
|
64
|
+
assert qr.body == {
|
65
|
+
"apiVersion": "v1",
|
66
|
+
"data": {"token": "token"},
|
67
|
+
"kind": "Secret",
|
68
|
+
"metadata": {"name": "foobar"},
|
69
|
+
"type": "Opaque",
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
def test_openshift_serviceaccount_tokens__get_tokens_for_service_account() -> None:
|
74
|
+
foobar_secret = {
|
75
|
+
"metadata": {
|
76
|
+
"annotations": {"kubernetes.io/service-account.name": "foobar-account"}
|
77
|
+
},
|
78
|
+
"type": "kubernetes.io/service-account-token",
|
79
|
+
}
|
80
|
+
assert get_tokens_for_service_account(
|
81
|
+
service_account="foobar-account",
|
82
|
+
tokens=[
|
83
|
+
foobar_secret,
|
84
|
+
{
|
85
|
+
"metadata": {
|
86
|
+
"annotations": {
|
87
|
+
"kubernetes.io/service-account.name": "just-another-account"
|
88
|
+
}
|
89
|
+
},
|
90
|
+
"type": "kubernetes.io/service-account-token",
|
91
|
+
},
|
92
|
+
foobar_secret,
|
93
|
+
{
|
94
|
+
"metadata": {"annotations": {"whatever": "foobar-account"}},
|
95
|
+
"type": "not-a-service-account-token",
|
96
|
+
},
|
97
|
+
],
|
98
|
+
) == [foobar_secret, foobar_secret]
|
99
|
+
|
100
|
+
|
101
|
+
def test_openshift_serviceaccount_tokens__write_outputs_to_vault(
|
102
|
+
mocker: MockerFixture, ri: ResourceInventory
|
103
|
+
) -> None:
|
104
|
+
vault_client = mocker.create_autospec(_VaultClient)
|
105
|
+
|
106
|
+
ri.add_desired(
|
107
|
+
cluster="cluster",
|
108
|
+
namespace="namespace",
|
109
|
+
resource_type="Secret",
|
110
|
+
name="name",
|
111
|
+
value=construct_sa_token_oc_resource("name", "token"),
|
112
|
+
)
|
113
|
+
write_outputs_to_vault(vault_client, "path/to/secrets", ri)
|
114
|
+
vault_client.write.call_count == 2
|
115
|
+
vault_client.write.assert_has_calls([
|
116
|
+
call({
|
117
|
+
"path": "path/to/secrets/openshift-serviceaccount-tokens/cluster/namespace/name",
|
118
|
+
"data": {"token": "token"},
|
119
|
+
}),
|
120
|
+
call({
|
121
|
+
"path": "path/to/secrets/openshift-serviceaccount-tokens/shared-resources/name",
|
122
|
+
"data": {"token": "token"},
|
123
|
+
}),
|
124
|
+
])
|
125
|
+
|
126
|
+
|
127
|
+
def test_openshift_serviceaccount_tokens__get_namespaces_with_serviceaccount_tokens(
|
128
|
+
namespaces: list[NamespaceV1],
|
129
|
+
) -> None:
|
130
|
+
assert len(namespaces) == 3
|
131
|
+
assert namespaces[0].name == "with-openshift-serviceaccount-tokens"
|
132
|
+
assert namespaces[1].name == "with-shared-resources"
|
133
|
+
assert (
|
134
|
+
namespaces[2].name
|
135
|
+
== "with-openshift-serviceaccount-tokens-and-shared-resources"
|
136
|
+
)
|
137
|
+
|
138
|
+
|
139
|
+
def test_openshift_serviceaccount_tokens__canonicalize_namespaces(
|
140
|
+
namespaces: list[NamespaceV1],
|
141
|
+
) -> None:
|
142
|
+
nss = canonicalize_namespaces(namespaces)
|
143
|
+
# sort by number of tokens and namespace name
|
144
|
+
nss.sort(key=lambda n: f"{len(n.openshift_service_account_tokens or [])}-{n.name}")
|
145
|
+
assert len(nss) == 6
|
146
|
+
|
147
|
+
# added via remote service account token
|
148
|
+
assert nss[0].name == "observability"
|
149
|
+
assert nss[0].openshift_service_account_tokens is None
|
150
|
+
assert nss[1].name == "platform-changelog-stage"
|
151
|
+
assert nss[1].openshift_service_account_tokens is None
|
152
|
+
assert nss[2].name == "with-openshift-serviceaccount-tokens"
|
153
|
+
assert nss[2].openshift_service_account_tokens is None
|
154
|
+
|
155
|
+
# namespace with tokens or shared resources defined
|
156
|
+
nss[3].name == "with-shared-resources"
|
157
|
+
nss[3].cluster.name == "cluster"
|
158
|
+
assert len(nss[3].openshift_service_account_tokens or []) == 1
|
159
|
+
|
160
|
+
nss[4].name == "with-openshift-serviceaccount-tokens"
|
161
|
+
assert len(nss[4].openshift_service_account_tokens or []) == 2
|
162
|
+
|
163
|
+
nss[5].name == "with-openshift-serviceaccount-tokens-and-shared-resources"
|
164
|
+
assert len(nss[5].openshift_service_account_tokens or []) == 2
|
165
|
+
|
166
|
+
|
167
|
+
def test_openshift_serviceaccount_tokens__fetch_desired_state(
|
168
|
+
mocker: MockerFixture, namespaces: list[NamespaceV1], ri: ResourceInventory
|
169
|
+
) -> None:
|
170
|
+
grafana_secret = {
|
171
|
+
"metadata": {
|
172
|
+
"name": "grafana-secret",
|
173
|
+
"annotations": {"kubernetes.io/service-account.name": "grafana"},
|
174
|
+
},
|
175
|
+
"type": "kubernetes.io/service-account-token",
|
176
|
+
"data": {"token": "super-secret-token"},
|
177
|
+
}
|
178
|
+
|
179
|
+
oc_map = mocker.create_autospec(OC_Map)
|
180
|
+
oc = mocker.create_autospec(OCCli)
|
181
|
+
oc_map.get.return_value = oc
|
182
|
+
oc.get_items.return_value = [
|
183
|
+
grafana_secret,
|
184
|
+
{
|
185
|
+
"metadata": {
|
186
|
+
"name": "just-another-account-secret",
|
187
|
+
"annotations": {
|
188
|
+
"kubernetes.io/service-account.name": "just-another-account"
|
189
|
+
},
|
190
|
+
},
|
191
|
+
"type": "kubernetes.io/service-account-token",
|
192
|
+
},
|
193
|
+
grafana_secret,
|
194
|
+
{
|
195
|
+
"metadata": {
|
196
|
+
"name": "just-something-different",
|
197
|
+
"annotations": {"whatever": "grafana"},
|
198
|
+
},
|
199
|
+
"type": "not-a-service-account-token",
|
200
|
+
},
|
201
|
+
]
|
202
|
+
fetch_desired_state(
|
203
|
+
namespaces=namespaces,
|
204
|
+
ri=ri,
|
205
|
+
oc_map=oc_map,
|
206
|
+
)
|
207
|
+
assert (
|
208
|
+
len(
|
209
|
+
ri._clusters["cluster"]["with-openshift-serviceaccount-tokens"]["Secret"][
|
210
|
+
"desired"
|
211
|
+
].keys()
|
212
|
+
)
|
213
|
+
== 2
|
214
|
+
)
|
215
|
+
assert (
|
216
|
+
len(
|
217
|
+
ri._clusters["cluster"][
|
218
|
+
"with-openshift-serviceaccount-tokens-and-shared-resources"
|
219
|
+
]["Secret"]["desired"].keys()
|
220
|
+
)
|
221
|
+
== 1
|
222
|
+
)
|
223
|
+
assert (
|
224
|
+
"another-cluster-with-openshift-serviceaccount-tokens-grafana"
|
225
|
+
in ri._clusters["cluster"]["with-openshift-serviceaccount-tokens"]["Secret"][
|
226
|
+
"desired"
|
227
|
+
]
|
228
|
+
)
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc967.dist-info → qontract_reconcile-0.10.1rc968.dist-info}/top_level.txt
RENAMED
File without changes
|