qontract-reconcile 0.10.2.dev481__py3-none-any.whl → 0.10.2.dev484__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.dev481
3
+ Version: 0.10.2.dev484
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
@@ -8,7 +8,7 @@ reconcile/aws_iam_password_reset.py,sha256=5oajSspJVAvpGd445hKsxtEGYb75dM4l1_PJT
8
8
  reconcile/aws_support_cases_sos.py,sha256=G9g0oM6ohvuJ5N592ePjiPeaDug4_vapAy58RyG-S3Y,3478
9
9
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
10
10
  reconcile/checkpoint.py,sha256=Q5Ru36ZEwWw95ZkUXBf4VfvCmqDS9TcAF7QVroOf9Vk,5375
11
- reconcile/cli.py,sha256=EqwFVBlDDABGD0j35I4nnw2NSis1d3NqKrOi262B7v4,115258
11
+ reconcile/cli.py,sha256=ZHOM3LPTUyKJy-N7p9OSyrJsb8pWGCsUFthBvJgjUkE,115887
12
12
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=_OKz7K7HHw0-gzxeEma8PcUCtd70pRBy7JMoaAm8IVU,4940
13
13
  reconcile/cluster_deployment_mapper.py,sha256=aKroYLY6-nNxWi2jxDu7VRMuNZ3YgSI0J6ek7Fet2AQ,2241
14
14
  reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
@@ -56,7 +56,6 @@ reconcile/ocm_update_recommended_version.py,sha256=Vi3Y2sX-OQxx1mv_xiPQXnmrpsZzG
56
56
  reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=j4qthqx8qREB6mSbV9NT-Giq1Tu5y2EhPgIObkvmjyU,4371
57
57
  reconcile/openshift_base.py,sha256=ERc8BELzWr8PF8SQtBbcYkzvudLe676ecEmS09MYZz0,57929
58
58
  reconcile/openshift_cluster_bots.py,sha256=QBflmFjXaIY7bxNlI2qSI5sER3Jks-_FAhrPHschI4g,10939
59
- reconcile/openshift_clusterrolebindings.py,sha256=jwSaYQvUUY7noQGc148Dkqm6woYxvOEd1sume7k_sUk,6212
60
59
  reconcile/openshift_groups.py,sha256=XpIyhgnWY1XUQio1wi6sHoDtoMYdk-lpHp0-1d1RC7o,9471
61
60
  reconcile/openshift_limitranges.py,sha256=-MVmUWMMLUbX50GuZ-XClm4RIH8uIKNqAAMYD1IIs0I,4017
62
61
  reconcile/openshift_namespace_labels.py,sha256=zjQNini7qgb-x_0QRBfD1zVBeX_5Pgk8ixDkBA4v488,16241
@@ -67,7 +66,6 @@ reconcile/openshift_resourcequotas.py,sha256=0CSuCre3T2ON42Ku1UDhTRugfmUNBx8PILp
67
66
  reconcile/openshift_resources.py,sha256=YnhDxCvsp0muxEmULiqWhoar9EzxohTrnbY-U7oS5Hc,1603
68
67
  reconcile/openshift_resources_base.py,sha256=wOdQMLqaiQleQTsC8MH9nwpBKFffLcOuZthpOcc1heE,43096
69
68
  reconcile/openshift_rhcs_certs.py,sha256=UjBFX344n4eFXZmoEUCVeGECBowWTpbjNyPGrEzAmkA,11544
70
- reconcile/openshift_rolebindings.py,sha256=G4n7wWTGZ34OsRgen0L3EpdPXuFEdZ_1E-b3ROvSb4I,10824
71
69
  reconcile/openshift_routes.py,sha256=xnA34f32xDdkfV2MXIC1QURFJioQUsXT8AZBiY7iSP0,1298
72
70
  reconcile/openshift_saas_deploy.py,sha256=YQRIjnb-V6x1a0fUv2w3hqjMj5tyqRirzkG8DzknYdc,13159
73
71
  reconcile/openshift_saas_deploy_change_tester.py,sha256=6wU7rFCaTRU1Wj8Izi6ExLvQstwqDbr9n9YfyPcb6zQ,8854
@@ -80,7 +78,7 @@ reconcile/openshift_saas_deploy_trigger_upstream_jobs.py,sha256=TBMQXyMktbCV1uI5
80
78
  reconcile/openshift_serviceaccount_tokens.py,sha256=y8pW6j_0ELTDrahZ0Fr-6St5ww1iJWCrPdAW3O-8rRA,9034
81
79
  reconcile/openshift_tekton_resources.py,sha256=5mUWEQqU9RpMLQZxHOb6IkbbZzx4iJnaVubGi2xVJLs,16368
82
80
  reconcile/openshift_upgrade_watcher.py,sha256=93l8X1-RHNtL89GzPg1XWivWart49l_hpAVFChvT6Wg,6643
83
- reconcile/openshift_users.py,sha256=lxHYrOhKntLnRy5mVZfh_8XWvwZaUgzrDNqkoon905U,5508
81
+ reconcile/openshift_users.py,sha256=aQfScWJlvMcXc0ApeQMZiDYdmh8zPSAwG6v6XrEDYhw,7015
84
82
  reconcile/openshift_vault_secrets.py,sha256=Ax-_EBWWU1VRHYyKaUkGJkIjHGwWM3bZgjXL5CkPW8k,1883
85
83
  reconcile/quay_base.py,sha256=4gNca076ICJ2fDoTwCgcA3L-DqmVmBgHWLCubTk78uc,2832
86
84
  reconcile/quay_membership.py,sha256=tSS1j34RzoMXZJHmeuW3yVyM2E9onSha9SMJO3RcUMo,7694
@@ -253,6 +251,7 @@ reconcile/gql_definitions/cluster_auth_rhidp/clusters.py,sha256=f6h8AWhovxTwlqZC
253
251
  reconcile/gql_definitions/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
252
  reconcile/gql_definitions/common/alerting_services_settings.py,sha256=gjdxA7DoIPvFSCo-4m4xY5msxagNLcjlwNapt0RptSw,1792
255
253
  reconcile/gql_definitions/common/app_code_component_repos.py,sha256=EB_Ri23uTZdL47hoBXIr3RNvD1WSLF81EddAwoAAH-w,2029
254
+ reconcile/gql_definitions/common/app_interface_clusterrole.py,sha256=aLTUToIYoGggMT5B2MTH12IG9E3Gs2gI6xg2voMnHK0,2868
256
255
  reconcile/gql_definitions/common/app_interface_custom_messages.py,sha256=_EyRjSdfOj76gGyFWATNSrDmLWk1htVqPqtY5EubPaY,1995
257
256
  reconcile/gql_definitions/common/app_interface_dms_settings.py,sha256=1XU0IctKM8K5XaGz7XIvAQpfV7eCRmloL6DK7KAYjvw,2563
258
257
  reconcile/gql_definitions/common/app_interface_repo_settings.py,sha256=1kDNoj0SuqNxc_Xjr1oFAA5KQe8EuJOCGlu8yLArkdU,1749
@@ -448,6 +447,12 @@ reconcile/ocm_internal_notifications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
448
447
  reconcile/ocm_internal_notifications/integration.py,sha256=MX0nXFNMS5mUAPVYAxK8MfXJ-pYanvOgyK3M5iALCZ0,4480
449
448
  reconcile/ocm_labels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
450
449
  reconcile/ocm_labels/integration.py,sha256=3iAfeT_lycgKGZCrycIc6b9aofBRmcVlJivORAzy-mE,15298
450
+ reconcile/openshift_bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
451
+ reconcile/openshift_bindings/constants.py,sha256=ox61yleIBG68wx-oqPiS4ZRj3RaoafGpeOh0g5rxtBI,303
452
+ reconcile/openshift_bindings/models.py,sha256=rHnjN5gbmcrL7gqLZpTr1-9un6GaDnvm3IXnInGMRps,8872
453
+ reconcile/openshift_bindings/openshift_clusterrolebindings.py,sha256=QhkU6x05uQEQrn8odW3zn3aY9MDUNzl6UHfJpuRCG3o,4827
454
+ reconcile/openshift_bindings/openshift_rolebindings.py,sha256=7C4bUCPBG3QxQvuL4C33wCfrDjEHLpEQ243QFUeOUw0,5152
455
+ reconcile/openshift_bindings/utils.py,sha256=4o4XwGAOOzBFw7MpowfrqfUKA7H0IU0_D9hQgIMJdg0,542
451
456
  reconcile/oum/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
452
457
  reconcile/oum/base.py,sha256=WCFdHOHXLPrJcvxVqw6HjaJthT7olC5BQqqXlD4DM6c,13552
453
458
  reconcile/oum/labelset.py,sha256=oZ_nV2ca8GU8iZptPdIqgCKgzQ2Wq89H8OiTGRIkWhw,2202
@@ -523,6 +528,7 @@ reconcile/terraform_vpc_resources/merge_request.py,sha256=MFRG7JQojmCTgr-9rLXVot
523
528
  reconcile/terraform_vpc_resources/merge_request_manager.py,sha256=0_FT_DKE39DU2NJ2dWQVy6mCdIo4T7n2tSdklUyH6S0,4124
524
529
  reconcile/typed_queries/__init__.py,sha256=rRk4CyslLsBr4vAh1pIPgt6s3P4R1M9NSEPLnyQgBpk,61
525
530
  reconcile/typed_queries/alerting_services_settings.py,sha256=YKQd60O_2C_H103nLrYgcUInndM2vFypqW_NO706L2E,833
531
+ reconcile/typed_queries/app_interface_clusterroles.py,sha256=0pR9uebFTnfEukvumQyz2oomSLP9Z5ILoVVSNTEpULk,266
526
532
  reconcile/typed_queries/app_interface_custom_messages.py,sha256=bgSAJEzqee8aiPVCj_bIIqb4VTkrF0-vti1dos7ebEg,684
527
533
  reconcile/typed_queries/app_interface_deadmanssnitch_settings.py,sha256=znhfiAawUTYSWabZiKDKeexR_-48z9FZ44sxD-MwA14,638
528
534
  reconcile/typed_queries/app_interface_repo_url.py,sha256=9fhgWihjWNYOmK65irBWw9jdm7YPJNUWqZC1Ez__PFc,578
@@ -803,7 +809,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
803
809
  tools/saas_promotion_state/saas_promotion_state.py,sha256=uQv2QJAmUXP1g2GPIH30WTlvL9soY6m9lefpZEVDM5w,3965
804
810
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
805
811
  tools/sre_checkpoints/util.py,sha256=KcYVfa3UmJHVP_ocgrKe8NkrO5IDB9aWEDydSokPcRk,975
806
- qontract_reconcile-0.10.2.dev481.dist-info/METADATA,sha256=M9TKHf5Kbti2-70-Mj0QGQlVCAihy0yea_mC5JyNgrg,24901
807
- qontract_reconcile-0.10.2.dev481.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
808
- qontract_reconcile-0.10.2.dev481.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
809
- qontract_reconcile-0.10.2.dev481.dist-info/RECORD,,
812
+ qontract_reconcile-0.10.2.dev484.dist-info/METADATA,sha256=BlX3qhbod6w_uXAw5N_cu67mBLoQxpBI9AhsXBvluv4,24901
813
+ qontract_reconcile-0.10.2.dev484.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
814
+ qontract_reconcile-0.10.2.dev484.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
815
+ qontract_reconcile-0.10.2.dev484.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -776,16 +776,25 @@ def github_validator(ctx: click.Context) -> None:
776
776
  @use_jump_host()
777
777
  @click.pass_context
778
778
  def openshift_clusterrolebindings(
779
- ctx: click.Context, thread_pool_size: int, internal: bool, use_jump_host: bool
779
+ ctx: click.Context,
780
+ thread_pool_size: int,
781
+ internal: bool,
782
+ use_jump_host: bool,
780
783
  ) -> None:
781
- import reconcile.openshift_clusterrolebindings
784
+ from reconcile.openshift_bindings.openshift_clusterrolebindings import (
785
+ OpenShiftClusterRoleBindingsIntegration,
786
+ OpenShiftClusterRoleBindingsIntegrationParams,
787
+ )
782
788
 
783
- run_integration(
784
- reconcile.openshift_clusterrolebindings,
785
- ctx,
786
- thread_pool_size,
787
- internal,
788
- use_jump_host,
789
+ run_class_integration(
790
+ integration=OpenShiftClusterRoleBindingsIntegration(
791
+ OpenShiftClusterRoleBindingsIntegrationParams(
792
+ thread_pool_size=thread_pool_size,
793
+ internal=internal,
794
+ use_jump_host=use_jump_host,
795
+ )
796
+ ),
797
+ ctx=ctx,
789
798
  )
790
799
 
791
800
 
@@ -808,15 +817,21 @@ def openshift_rolebindings(
808
817
  use_jump_host: bool,
809
818
  support_role_ref: bool,
810
819
  ) -> None:
811
- import reconcile.openshift_rolebindings
820
+ from reconcile.openshift_bindings.openshift_rolebindings import (
821
+ OpenShiftRoleBindingsIntegration,
822
+ OpenShiftRoleBindingsIntegrationParams,
823
+ )
812
824
 
813
- run_integration(
814
- reconcile.openshift_rolebindings,
815
- ctx,
816
- thread_pool_size,
817
- internal,
818
- use_jump_host,
819
- support_role_ref,
825
+ run_class_integration(
826
+ integration=OpenShiftRoleBindingsIntegration(
827
+ OpenShiftRoleBindingsIntegrationParams(
828
+ thread_pool_size=thread_pool_size,
829
+ internal=internal,
830
+ use_jump_host=use_jump_host,
831
+ support_role_ref=support_role_ref,
832
+ )
833
+ ),
834
+ ctx=ctx,
820
835
  )
821
836
 
822
837
 
@@ -0,0 +1,104 @@
1
+ """
2
+ Generated by qenerate plugin=pydantic_v2. 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
+ ConfigDict,
16
+ Field,
17
+ Json,
18
+ )
19
+
20
+
21
+ DEFINITION = """
22
+ query AppInterfaceClusterRoles {
23
+ clusterRoles: roles_v1 {
24
+ name
25
+ users {
26
+ org_username
27
+ github_username
28
+ }
29
+ bots {
30
+ openshift_serviceaccount
31
+ }
32
+ access {
33
+ cluster {
34
+ name
35
+ auth {
36
+ service
37
+ }
38
+ }
39
+ clusterRole
40
+ }
41
+ expirationDate
42
+ }
43
+ }
44
+ """
45
+
46
+
47
+ class ConfiguredBaseModel(BaseModel):
48
+ model_config = ConfigDict(
49
+ extra='forbid'
50
+ )
51
+
52
+
53
+ class UserV1(ConfiguredBaseModel):
54
+ org_username: str = Field(..., alias="org_username")
55
+ github_username: str = Field(..., alias="github_username")
56
+
57
+
58
+ class BotV1(ConfiguredBaseModel):
59
+ openshift_serviceaccount: Optional[str] = Field(..., alias="openshift_serviceaccount")
60
+
61
+
62
+ class ClusterAuthV1(ConfiguredBaseModel):
63
+ service: str = Field(..., alias="service")
64
+
65
+
66
+ class ClusterV1(ConfiguredBaseModel):
67
+ name: str = Field(..., alias="name")
68
+ auth: list[ClusterAuthV1] = Field(..., alias="auth")
69
+
70
+
71
+ class AccessV1(ConfiguredBaseModel):
72
+ cluster: Optional[ClusterV1] = Field(..., alias="cluster")
73
+ cluster_role: Optional[str] = Field(..., alias="clusterRole")
74
+
75
+
76
+ class RoleV1(ConfiguredBaseModel):
77
+ name: str = Field(..., alias="name")
78
+ users: list[UserV1] = Field(..., alias="users")
79
+ bots: list[BotV1] = Field(..., alias="bots")
80
+ access: Optional[list[AccessV1]] = Field(..., alias="access")
81
+ expiration_date: Optional[str] = Field(..., alias="expirationDate")
82
+
83
+
84
+ class AppInterfaceClusterRolesQueryData(ConfiguredBaseModel):
85
+ cluster_roles: Optional[list[RoleV1]] = Field(..., alias="clusterRoles")
86
+
87
+
88
+ def query(query_func: Callable, **kwargs: Any) -> AppInterfaceClusterRolesQueryData:
89
+ """
90
+ This is a convenience function which queries and parses the data into
91
+ concrete types. It should be compatible with most GQL clients.
92
+ You do not have to use it to consume the generated data classes.
93
+ Alternatively, you can also mime and alternate the behavior
94
+ of this function in the caller.
95
+
96
+ Parameters:
97
+ query_func (Callable): Function which queries your GQL Server
98
+ kwargs: optional arguments that will be passed to the query function
99
+
100
+ Returns:
101
+ AppInterfaceClusterRolesQueryData: queried data parsed into generated classes
102
+ """
103
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
104
+ return AppInterfaceClusterRolesQueryData(**raw_data)
File without changes
@@ -0,0 +1,7 @@
1
+ OPENSHIFT_ROLEBINDINGS_INTEGRATION_NAME = "openshift-rolebindings"
2
+ OPENSHIFT_CLUSTERROLEBINDINGS_INTEGRATION_NAME = "openshift-clusterrolebindings"
3
+
4
+ ROLE_BINDING_RESOURCE_KIND = "RoleBinding"
5
+ CLUSTER_ROLE_BINDING_RESOURCE_KIND = "ClusterRoleBinding"
6
+ ROLE_KIND = "Role"
7
+ CLUSTER_ROLE_KIND = "ClusterRole"
@@ -0,0 +1,264 @@
1
+ """Shared data models for openshift-rolebindings and openshift-clusterrolebindings integrations."""
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any, Self
5
+
6
+ from pydantic import BaseModel
7
+
8
+ import reconcile.openshift_base as ob
9
+ from reconcile.gql_definitions.common.app_interface_clusterrole import (
10
+ BotV1 as ClusterBotV1,
11
+ )
12
+ from reconcile.gql_definitions.common.app_interface_clusterrole import (
13
+ ClusterV1 as ClusterRoleClusterV1,
14
+ )
15
+ from reconcile.gql_definitions.common.app_interface_clusterrole import (
16
+ RoleV1 as ClusterRoleV1,
17
+ )
18
+ from reconcile.gql_definitions.common.app_interface_clusterrole import (
19
+ UserV1 as ClusterUserV1,
20
+ )
21
+ from reconcile.gql_definitions.common.app_interface_roles import (
22
+ AccessV1,
23
+ NamespaceV1,
24
+ RoleV1,
25
+ UserV1,
26
+ )
27
+ from reconcile.gql_definitions.common.app_interface_roles import (
28
+ BotV1 as RoleBotV1,
29
+ )
30
+ from reconcile.gql_definitions.common.app_interface_roles import (
31
+ ClusterV1 as RoleClusterV1,
32
+ )
33
+ from reconcile.openshift_bindings.constants import (
34
+ CLUSTER_ROLE_BINDING_RESOURCE_KIND,
35
+ CLUSTER_ROLE_KIND,
36
+ ROLE_BINDING_RESOURCE_KIND,
37
+ ROLE_KIND,
38
+ )
39
+ from reconcile.openshift_bindings.utils import is_valid_namespace
40
+ from reconcile.utils.openshift_resource import OpenshiftResource as OR
41
+
42
+
43
+ def get_usernames_from_users(
44
+ users: Sequence[UserV1 | ClusterUserV1] | None,
45
+ user_keys: list[str] | None = None,
46
+ ) -> set[str]:
47
+ return {
48
+ name
49
+ for user in users or []
50
+ for user_key in user_keys or []
51
+ if (name := getattr(user, user_key, None))
52
+ }
53
+
54
+
55
+ class OCResource(BaseModel, arbitrary_types_allowed=True):
56
+ """Represents an OpenShift resource with metadata."""
57
+
58
+ resource: OR
59
+ resource_name: str
60
+ privileged: bool = False
61
+
62
+
63
+ class OCResourceData(BaseModel):
64
+ body: dict[str, Any]
65
+ name: str
66
+
67
+
68
+ class ServiceAccountSpec(BaseModel):
69
+ """Service account specification with namespace and name."""
70
+
71
+ sa_namespace_name: str
72
+ sa_name: str
73
+
74
+ @classmethod
75
+ def from_bots(cls, bots: Sequence[RoleBotV1 | ClusterBotV1] | None) -> list[Self]:
76
+ """Create ServiceAccountSpec list from bot configurations."""
77
+ return [
78
+ cls(
79
+ sa_namespace_name=full_service_account[0],
80
+ sa_name=full_service_account[1],
81
+ )
82
+ for bot in bots or []
83
+ if bot.openshift_serviceaccount
84
+ and (full_service_account := bot.openshift_serviceaccount.split("/"))
85
+ and len(full_service_account) == 2
86
+ ]
87
+
88
+
89
+ class BindingSpec(BaseModel, arbitrary_types_allowed=True):
90
+ """Base specification for role bindings (cluster or namespace scoped)."""
91
+
92
+ role_name: str
93
+ role_kind: str # "Role" or "ClusterRole"
94
+ cluster: RoleClusterV1 | ClusterRoleClusterV1
95
+ resource_kind: str
96
+ usernames: set[str]
97
+ openshift_service_accounts: list[ServiceAccountSpec]
98
+
99
+ def get_oc_resources(self) -> list[OCResourceData]:
100
+ user_oc_resources = [
101
+ self.construct_user_oc_resource(username) for username in self.usernames
102
+ ]
103
+ sa_oc_resources = [
104
+ self.construct_sa_oc_resource(sa.sa_namespace_name, sa.sa_name)
105
+ for sa in self.openshift_service_accounts
106
+ ]
107
+ return user_oc_resources + sa_oc_resources
108
+
109
+ def construct_user_oc_resource(self, username: str) -> OCResourceData:
110
+ name = f"{self.role_name}-{username}"
111
+ body: dict[str, Any] = {
112
+ "apiVersion": "rbac.authorization.k8s.io/v1",
113
+ "kind": self.resource_kind,
114
+ "metadata": {"name": name},
115
+ "roleRef": {"kind": self.role_kind, "name": self.role_name},
116
+ "subjects": [{"kind": "User", "name": username}],
117
+ }
118
+ return OCResourceData(body=body, name=name)
119
+
120
+ def construct_sa_oc_resource(
121
+ self, sa_namespace_name: str, sa_name: str
122
+ ) -> OCResourceData:
123
+ name = f"{self.role_name}-{sa_namespace_name}-{sa_name}"
124
+ body: dict[str, Any] = {
125
+ "apiVersion": "rbac.authorization.k8s.io/v1",
126
+ "kind": self.resource_kind,
127
+ "metadata": {"name": name},
128
+ "roleRef": {"kind": self.role_kind, "name": self.role_name},
129
+ "subjects": [
130
+ {
131
+ "kind": "ServiceAccount",
132
+ "name": sa_name,
133
+ "namespace": sa_namespace_name,
134
+ }
135
+ ],
136
+ }
137
+ return OCResourceData(body=body, name=name)
138
+
139
+ def get_openshift_resources(
140
+ self,
141
+ integration_name: str,
142
+ integration_version: str,
143
+ privileged: bool = False,
144
+ ) -> list[OCResource]:
145
+ oc_resources = [
146
+ OCResource(
147
+ resource=OR(
148
+ oc_resource_data.body,
149
+ integration_name,
150
+ integration_version,
151
+ error_details=oc_resource_data.name,
152
+ ),
153
+ resource_name=oc_resource_data.name,
154
+ privileged=privileged,
155
+ )
156
+ for oc_resource_data in self.get_oc_resources()
157
+ ]
158
+ return oc_resources
159
+
160
+
161
+ class RoleBindingSpec(BindingSpec):
162
+ """Namespace-scoped RoleBinding specification."""
163
+
164
+ namespace: NamespaceV1
165
+ privileged: bool = False
166
+
167
+ @classmethod
168
+ def create_role_binding_spec(
169
+ cls,
170
+ access: AccessV1,
171
+ users: list[UserV1] | None = None,
172
+ enforced_user_keys: list[str] | None = None,
173
+ bots: list[RoleBotV1] | None = None,
174
+ support_role_ref: bool = False,
175
+ ) -> Self | None:
176
+ """Create a RoleBindingSpec from access configuration."""
177
+ if not access.namespace:
178
+ return None
179
+ if not (access.role or access.cluster_role):
180
+ return None
181
+ privileged = access.namespace.cluster_admin or False
182
+ auth_dict = [
183
+ auth.model_dump(by_alias=True) for auth in access.namespace.cluster.auth
184
+ ]
185
+ usernames = get_usernames_from_users(
186
+ users,
187
+ ob.determine_user_keys_for_access(
188
+ access.namespace.cluster.name,
189
+ auth_dict,
190
+ enforced_user_keys,
191
+ ),
192
+ )
193
+ service_accounts = ServiceAccountSpec.from_bots(bots) if bots else []
194
+ role_kind = ROLE_KIND if access.role and support_role_ref else CLUSTER_ROLE_KIND
195
+ return cls(
196
+ role_name=access.role or access.cluster_role,
197
+ role_kind=role_kind,
198
+ namespace=access.namespace,
199
+ cluster=access.namespace.cluster,
200
+ privileged=privileged,
201
+ usernames=usernames,
202
+ openshift_service_accounts=service_accounts,
203
+ resource_kind=ROLE_BINDING_RESOURCE_KIND,
204
+ )
205
+
206
+ @classmethod
207
+ def create_rb_specs_from_role(
208
+ cls,
209
+ role: RoleV1,
210
+ enforced_user_keys: list[str] | None = None,
211
+ support_role_ref: bool = False,
212
+ ) -> list[Self]:
213
+ """Create list of RoleBindingSpec from a role configuration."""
214
+ rolebinding_spec_list = [
215
+ role_binding_spec
216
+ for access in role.access or []
217
+ if (
218
+ access.namespace
219
+ and is_valid_namespace(access.namespace)
220
+ and (
221
+ role_binding_spec := cls.create_role_binding_spec(
222
+ access,
223
+ role.users,
224
+ enforced_user_keys,
225
+ role.bots,
226
+ support_role_ref,
227
+ )
228
+ )
229
+ )
230
+ ]
231
+ return rolebinding_spec_list
232
+
233
+
234
+ class ClusterRoleBindingSpec(BindingSpec):
235
+ """Cluster-scoped ClusterRoleBinding specification."""
236
+
237
+ @classmethod
238
+ def create_cluster_role_binding_specs(
239
+ cls, cluster_role: ClusterRoleV1
240
+ ) -> list[Self]:
241
+ cluster_role_binding_specs = [
242
+ cls(
243
+ cluster=access.cluster,
244
+ usernames=get_usernames_from_users(
245
+ users=cluster_role.users,
246
+ user_keys=cls.get_user_keys(access.cluster),
247
+ ),
248
+ openshift_service_accounts=ServiceAccountSpec.from_bots(
249
+ cluster_role.bots
250
+ ),
251
+ role_name=access.cluster_role,
252
+ role_kind=CLUSTER_ROLE_KIND,
253
+ resource_kind=CLUSTER_ROLE_BINDING_RESOURCE_KIND,
254
+ )
255
+ for access in cluster_role.access or []
256
+ if access.cluster and access.cluster_role
257
+ ]
258
+ return cluster_role_binding_specs
259
+
260
+ @classmethod
261
+ def get_user_keys(cls, cluster: ClusterRoleClusterV1) -> list[str] | None:
262
+ auth_dict = [auth.model_dump(by_alias=True) for auth in cluster.auth]
263
+ user_keys = ob.determine_user_keys_for_access(cluster.name, auth_dict)
264
+ return user_keys
@@ -0,0 +1,129 @@
1
+ """OpenShift ClusterRoleBindings integration.
2
+
3
+ Manages cluster-scoped ClusterRoleBindings across OpenShift clusters.
4
+ """
5
+
6
+ import sys
7
+ from collections.abc import Callable
8
+ from typing import TYPE_CHECKING
9
+
10
+ import reconcile.openshift_base as ob
11
+ from reconcile.openshift_bindings.constants import (
12
+ OPENSHIFT_CLUSTERROLEBINDINGS_INTEGRATION_NAME,
13
+ )
14
+ from reconcile.openshift_bindings.models import ClusterRoleBindingSpec
15
+ from reconcile.typed_queries.app_interface_clusterroles import (
16
+ get_app_interface_clusterroles,
17
+ )
18
+ from reconcile.typed_queries.clusters import get_clusters
19
+ from reconcile.utils import expiration
20
+ from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
21
+ from reconcile.utils.defer import defer
22
+ from reconcile.utils.oc import OC_Map
23
+ from reconcile.utils.openshift_resource import ResourceInventory
24
+ from reconcile.utils.runtime.integration import (
25
+ PydanticRunParams,
26
+ QontractReconcileIntegration,
27
+ )
28
+ from reconcile.utils.semver_helper import make_semver
29
+
30
+ if TYPE_CHECKING:
31
+ from reconcile.gql_definitions.common.app_interface_clusterrole import RoleV1
32
+
33
+ QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
34
+ QONTRACT_INTEGRATION_MANAGED_TYPE = "ClusterRoleBinding.rbac.authorization.k8s.io"
35
+ NAMESPACE_CLUSTER_SCOPE = "cluster"
36
+
37
+
38
+ class OpenShiftClusterRoleBindingsIntegrationParams(PydanticRunParams):
39
+ thread_pool_size: int = DEFAULT_THREAD_POOL_SIZE
40
+ internal: bool | None = None
41
+ use_jump_host: bool = True
42
+
43
+
44
+ class OpenShiftClusterRoleBindingsIntegration(
45
+ QontractReconcileIntegration[OpenShiftClusterRoleBindingsIntegrationParams],
46
+ ):
47
+ """Manages ClusterRoleBindings across OpenShift clusters."""
48
+
49
+ @defer
50
+ def run(self, dry_run: bool, defer: Callable | None = None) -> None:
51
+ ri, oc_map = self.fetch_current_state()
52
+ if defer:
53
+ defer(oc_map.cleanup)
54
+ self.fetch_desired_state(
55
+ ri,
56
+ allowed_clusters=set(oc_map.clusters()),
57
+ )
58
+ ob.publish_metrics(ri, self.name)
59
+ ob.realize_data(dry_run, oc_map, ri, self.params.thread_pool_size)
60
+ if ri.has_error_registered():
61
+ sys.exit(1)
62
+
63
+ @property
64
+ def name(self) -> str:
65
+ return OPENSHIFT_CLUSTERROLEBINDINGS_INTEGRATION_NAME
66
+
67
+ @property
68
+ def integration_version(self) -> str:
69
+ return QONTRACT_INTEGRATION_VERSION
70
+
71
+ def fetch_current_state(self) -> tuple[ResourceInventory, OC_Map]:
72
+ clusters = [
73
+ cluster.model_dump(by_alias=True)
74
+ for cluster in get_clusters()
75
+ if cluster.managed_cluster_roles and cluster.automation_token is not None
76
+ ]
77
+ return ob.fetch_current_state(
78
+ clusters=clusters,
79
+ thread_pool_size=self.params.thread_pool_size,
80
+ integration=self.name,
81
+ integration_version=self.integration_version,
82
+ override_managed_types=[QONTRACT_INTEGRATION_MANAGED_TYPE],
83
+ internal=self.params.internal,
84
+ use_jump_host=self.params.use_jump_host,
85
+ )
86
+
87
+ def fetch_desired_state(
88
+ self,
89
+ ri: ResourceInventory | None,
90
+ allowed_clusters: set[str] | None = None,
91
+ ) -> None:
92
+ if allowed_clusters is not None and not allowed_clusters:
93
+ return
94
+ if ri is None:
95
+ return
96
+ cluster_roles: list[RoleV1] = expiration.filter(
97
+ get_app_interface_clusterroles()
98
+ )
99
+ cluster_role_binding_specs = [
100
+ cluster_role_binding_spec
101
+ for cluster_role in cluster_roles
102
+ for cluster_role_binding_spec in ClusterRoleBindingSpec.create_cluster_role_binding_specs(
103
+ cluster_role
104
+ )
105
+ ]
106
+ if allowed_clusters:
107
+ cluster_role_binding_specs = [
108
+ cluster_role_binding_spec
109
+ for cluster_role_binding_spec in cluster_role_binding_specs
110
+ if cluster_role_binding_spec.cluster.name in allowed_clusters
111
+ ]
112
+ for cluster_role_binding_spec in cluster_role_binding_specs:
113
+ for oc_resource in cluster_role_binding_spec.get_openshift_resources(
114
+ self.name,
115
+ self.integration_version,
116
+ ):
117
+ if not ri.get_desired(
118
+ cluster_role_binding_spec.cluster.name,
119
+ NAMESPACE_CLUSTER_SCOPE,
120
+ QONTRACT_INTEGRATION_MANAGED_TYPE,
121
+ oc_resource.resource_name,
122
+ ):
123
+ ri.add_desired(
124
+ cluster=cluster_role_binding_spec.cluster.name,
125
+ namespace=NAMESPACE_CLUSTER_SCOPE,
126
+ resource_type=QONTRACT_INTEGRATION_MANAGED_TYPE,
127
+ name=oc_resource.resource_name,
128
+ value=oc_resource.resource,
129
+ )