qontract-reconcile 0.10.2.dev186__py3-none-any.whl → 0.10.2.dev187__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.dev186
3
+ Version: 0.10.2.dev187
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
@@ -26,7 +26,7 @@ reconcile/github_org.py,sha256=Wc5cZamatuWsW2ZJT2ib5ps8l3iY3RXHwNUxVJerqz0,14173
26
26
  reconcile/github_owners.py,sha256=viE1KJ-zaTxuZ5yItg2C263J0brn-Q-3hR_DkYDMbhY,3122
27
27
  reconcile/github_repo_invites.py,sha256=U9UCzNVwrZ7MqODtFah8ogH0NNY-XjBin7G9gqHtCUY,2690
28
28
  reconcile/github_repo_permissions_validator.py,sha256=PNqL4dqa2OaNBy-NmLVN-t1HZa6eS6HgSYmfOunYqtA,1798
29
- reconcile/github_users.py,sha256=QdX164LZrm8sqggMj-0beCzWofpS6OEBfzKNrWPrfj0,3934
29
+ reconcile/github_users.py,sha256=ZeYNMyvZPVMx6mh5TiKEUQy4W1uw3VmUZOHiXus5I0Y,5073
30
30
  reconcile/github_validator.py,sha256=-j17tn3csFVjPMSPL3te48iWVkPZCncRXdeKeLdGjjQ,931
31
31
  reconcile/gitlab_fork_compliance.py,sha256=RbHckzLnE9zkOFHJANzoejEMMbMAivmqJVs3Suvp9lU,4591
32
32
  reconcile/gitlab_housekeeping.py,sha256=c31Jtw5t8bnOzUO9jMWF_0DHitPzol93AA7YWBxM5L0,25416
@@ -47,7 +47,7 @@ reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffF
47
47
  reconcile/jenkins_worker_fleets.py,sha256=L2wEXpd4xuEHrXGss4iH788nG8UlLSYduZe1EY2IVw4,5377
48
48
  reconcile/jira_permissions_validator.py,sha256=5rc4Q2mXGL3HCZmYpZaJkjzBrpCRnlLeCY0Yl2fDOs4,14672
49
49
  reconcile/jira_watcher.py,sha256=L_UL2MKm2SoIGNsCLThm28pnqCkoFc154JWsD6bURug,3593
50
- reconcile/ldap_users.py,sha256=7hdO5CAPl-VNBvDRmKHg13LoblHXXPt7YEKNGomAoGg,3158
50
+ reconcile/ldap_users.py,sha256=oP1CAxmgSi3zDJ3vKTPySjap6WmEX1U469FmFrov5l4,4599
51
51
  reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
52
52
  reconcile/ocm_additional_routers.py,sha256=KfcFDVbNoc6n5dHWjYdAf1_DiVqVG6Tw23WLKoV8cdg,3306
53
53
  reconcile/ocm_addons.py,sha256=qqAyqRBRbdZQvAcjb-QlSVyRAyQBZk6iVlgnI4jyi7s,3353
@@ -284,6 +284,7 @@ reconcile/gql_definitions/common/clusters_with_peering.py,sha256=B1Hi3u6rZZsl4bD
284
284
  reconcile/gql_definitions/common/github_orgs.py,sha256=rZ0pDAA2_9hF9N-ykRZIxPtEmczTSjuA_k3nkp0k1W0,2039
285
285
  reconcile/gql_definitions/common/jira_settings.py,sha256=Fmjxhlhr69kc4jkG_0k17fuYlQVucbNex0jXYu83wbY,1990
286
286
  reconcile/gql_definitions/common/jiralert_settings.py,sha256=H96nMg_r2YcOvioj3aIkwqtFrALGSLt7uhbx9jGSUTo,1984
287
+ reconcile/gql_definitions/common/ldap_settings.py,sha256=qkKm3BusR4FreHcI9VSwPg-hrhBKtiQIjGsTAGvHDG4,1885
287
288
  reconcile/gql_definitions/common/namespaces.py,sha256=FUgyoDAKWSetfDumfqHRVT6lOGp9hoj1-8rC0khVJI4,11071
288
289
  reconcile/gql_definitions/common/namespaces_minimal.py,sha256=XVt8LFe-bGYbjN3ysX3b9sFGmLX4snQ_A9ZouQGaaAI,3429
289
290
  reconcile/gql_definitions/common/ocm_env_telemeter.py,sha256=jW0Q9WazDQVOxh4u0LMFG69rupBioJ8HGGjvR9bVK9Y,2424
@@ -301,6 +302,7 @@ reconcile/gql_definitions/common/slack_workspaces.py,sha256=2o0kgi4QiaRuNmZJnc_B
301
302
  reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
302
303
  reconcile/gql_definitions/common/state_aws_account.py,sha256=LAdpCG2-ykVpWBPO0Zu1WvG-hwKXyDC0fJQxJRpbqCk,2198
303
304
  reconcile/gql_definitions/common/users.py,sha256=ahY3d185LbTekCGYBLJwZJljn54RJI_P5CVefdqyoZA,1705
305
+ reconcile/gql_definitions/common/users_with_paths.py,sha256=kOYzIXKG11JI0afgRMA5WLlEwJlvElAKytE8RAeuSco,2726
304
306
  reconcile/gql_definitions/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
305
307
  reconcile/gql_definitions/cost_report/app_names.py,sha256=fzqYXyiTSll359J1F1o7qapco0MSxgs3sr_Ssb2Kbns,1786
306
308
  reconcile/gql_definitions/cost_report/cost_namespaces.py,sha256=URRozAgSa9OnkqOCZf3MGH21_wcnsqYl0n-olXdjQH0,2286
@@ -465,7 +467,7 @@ reconcile/oum/base.py,sha256=WCFdHOHXLPrJcvxVqw6HjaJthT7olC5BQqqXlD4DM6c,13552
465
467
  reconcile/oum/labelset.py,sha256=f5kDndbaIT4iNYxTRPSELTUgj_aMlzEJDPzooAkG2mE,2154
466
468
  reconcile/oum/metrics.py,sha256=S_0C-hIW4jHVl9Lltgis9q-p33fdBjADWBouQ9Emeao,1575
467
469
  reconcile/oum/models.py,sha256=teH0bJTCMTzbdbYD9CU4yXDuMr34ceLcM0KuoIPU8gI,1712
468
- reconcile/oum/providers.py,sha256=3kEjXvsTPzXc7gzrdO7hWqgzcMmMZMpk2S0X7wQUTWU,1767
470
+ reconcile/oum/providers.py,sha256=lfG6d7YV-A4Lte45EMv1Gx4A346piJ_jAkrU5AHJZ_g,1834
469
471
  reconcile/oum/standalone.py,sha256=EN5y1S-3DwUZYzSRqRMtf63mI2slvBHKiU9zOTjYvWM,7334
470
472
  reconcile/prometheus_rules_tester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
471
473
  reconcile/prometheus_rules_tester/integration.py,sha256=8lXZSaNqZAemulVNDxanrxwl7ZGfUxtwfptJlMPX8ac,9308
@@ -561,6 +563,7 @@ reconcile/typed_queries/jenkins.py,sha256=Pus8Rhsb04F92Iqh31xe-rW1TLiEziIWd0UAth
561
563
  reconcile/typed_queries/jira.py,sha256=jq6-ERCr_Fh96_3i9A9UKfpAAstoc4Iz6Irl-_0IUkw,235
562
564
  reconcile/typed_queries/jira_settings.py,sha256=i0ddx5xxHrM1v-9mtL_6OB-jBFLw7-HS6xenpIDjrkw,570
563
565
  reconcile/typed_queries/jiralert_settings.py,sha256=y59S5xvYmuaGxszzfKhVLjbCyDwKiaSIlajocbK5MDE,793
566
+ reconcile/typed_queries/ldap_settings.py,sha256=k8LWM11wPHn6_x2TMP7xkiwQTOPBgzuYdjzygG1WVT4,664
564
567
  reconcile/typed_queries/namespaces.py,sha256=vItPrn7sfcHOix-VvkzQkf54_ljzI_ymyxh5esdBJ5Y,262
565
568
  reconcile/typed_queries/namespaces_minimal.py,sha256=rUtqNQ0ORXXUTQfnpsMURymAJ4gYtE77V-Lb3LiJFEY,278
566
569
  reconcile/typed_queries/ocm.py,sha256=aTXW9NaMpMq-90sBUAUQmGPtk6Hnsk2rzSbXv3pD8dY,312
@@ -577,6 +580,7 @@ reconcile/typed_queries/tekton_pipeline_providers.py,sha256=LtoSnSRkuckYsXIU64L1
577
580
  reconcile/typed_queries/terraform_namespaces.py,sha256=4H9WE90jN_BVYBAt1DxJITS4vkL-vykbXZIS1H4EKNM,413
578
581
  reconcile/typed_queries/unleash.py,sha256=7HDc4owF044xM9Thx4WsXV7DZgETxJjy4lbpwmqz1vU,282
579
582
  reconcile/typed_queries/users.py,sha256=UXlaxeZAoNIugMEndfcjbkHYowUURE72aWcdmxfb3yk,377
583
+ reconcile/typed_queries/users_with_paths.py,sha256=lvW0QzTJtKkLS2O_Jm9_0mFXJFKvGlZE809LI-5ddvc,342
580
584
  reconcile/typed_queries/vault.py,sha256=lkRsmobykorof3fcrIPLz-NgvAiSOWSOZc_jXBlnl1w,274
581
585
  reconcile/typed_queries/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
582
586
  reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py,sha256=X-N1WJGOL6OR9940P0_K4-YJzkL5Vg4favhYrBxXD9A,327
@@ -629,7 +633,7 @@ reconcile/utils/jjb_client.py,sha256=e5cDeNAeJMGz3sZMJ1KUIMFyLdRet0YnC0Qgj1vTPHc
629
633
  reconcile/utils/jsonpath.py,sha256=wdxOMqR-GMpQf5vRPWRMqAF7bCiXDBkkcFfY2U4j_tk,5536
630
634
  reconcile/utils/jump_host.py,sha256=gi8vGUDgdTVwJvROvRVauFxtL0YAramhbWvG70L7AY8,5137
631
635
  reconcile/utils/keycloak.py,sha256=YWSEUGrOVqFaJUk055dKUWpLDPdDRvhcmvR-lfbmxdE,3388
632
- reconcile/utils/ldap_client.py,sha256=sxvvSEwuswDexrTlLSz9GKQ4ym9OJr-yk2dnCM5cmxs,2463
636
+ reconcile/utils/ldap_client.py,sha256=HxOxpqyLRbXxOARZyBNbccceWIk6gF8PQjNQmBvjF2o,2148
633
637
  reconcile/utils/lean_terraform_client.py,sha256=X9358loxzkhwRExTeDv_NC8Q6HNr2tJK6Lx897YtJUc,4004
634
638
  reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
635
639
  reconcile/utils/metrics.py,sha256=kiOoWO0b0mO-MDZWxyClYz9SeohQ0QU-xji0p-cSiLo,18462
@@ -727,7 +731,7 @@ reconcile/utils/mr/ocm_update_recommended_version.py,sha256=p_aVP0TGrlKk9WBwgQnY
727
731
  reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py,sha256=5EncHGr4QRnZgHedRfCwMYZ9CaijYzHGj7-M6lhtQRo,3004
728
732
  reconcile/utils/mr/promote_qontract.py,sha256=wgvX2CBlcZaihKJSXJ0zcEK8NGaEP2_DUQDz0STzGes,7158
729
733
  reconcile/utils/mr/update_access_report_base.py,sha256=4Grohtp44v1sSHZyIAYOwClxH8SLj_nnOrCcHPKp9p0,4361
730
- reconcile/utils/mr/user_maintenance.py,sha256=ZlR1Id_r2BUXsoerJW-0Ioh5bcbwlnQxBBhSs-ri9Dk,5099
734
+ reconcile/utils/mr/user_maintenance.py,sha256=KSfl4i0k1CqCa9mj93bvFxHeBHPRMn3sBWdGS1nNzYY,5371
731
735
  reconcile/utils/ocm/__init__.py,sha256=Y-bp8GomMpyCo0tFW6kJ78-ZG1UIupYRtBzbMWU0kwM,798
732
736
  reconcile/utils/ocm/addons.py,sha256=_LDdJ-gapM3s5exKlIUt-MlXZTAUoHezbYBU0QmvfWQ,7335
733
737
  reconcile/utils/ocm/base.py,sha256=8rZ8WilNeAfq7HRNI8kNOLB4VcYzQpqQ5gvWbS54MuM,14576
@@ -805,7 +809,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
805
809
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
806
810
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
807
811
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
808
- qontract_reconcile-0.10.2.dev186.dist-info/METADATA,sha256=0hGybBCljHc9z_10Wcq6RvjgFFVSF7GPG9w5yhdMTTM,24555
809
- qontract_reconcile-0.10.2.dev186.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
810
- qontract_reconcile-0.10.2.dev186.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
811
- qontract_reconcile-0.10.2.dev186.dist-info/RECORD,,
812
+ qontract_reconcile-0.10.2.dev187.dist-info/METADATA,sha256=-iInkgy_ICSk6OCktOC27DWeGBEvZpYlsXpPFJ640IU,24555
813
+ qontract_reconcile-0.10.2.dev187.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
814
+ qontract_reconcile-0.10.2.dev187.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
815
+ qontract_reconcile-0.10.2.dev187.dist-info/RECORD,,
reconcile/github_users.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  import re
4
+ from collections import defaultdict
4
5
  from collections.abc import Callable
5
6
 
6
7
  from github import Github
@@ -17,9 +18,9 @@ from reconcile import (
17
18
  typed_queries,
18
19
  )
19
20
  from reconcile.github_org import get_default_config
20
- from reconcile.ldap_users import init_users as init_users_and_paths
21
21
  from reconcile.utils.defer import defer
22
22
  from reconcile.utils.mr import CreateDeleteUserAppInterface
23
+ from reconcile.utils.mr.user_maintenance import PathSpec, PathTypes
23
24
  from reconcile.utils.secret_reader import SecretReader
24
25
  from reconcile.utils.smtp_client import (
25
26
  DEFAULT_SMTP_TIMEOUT,
@@ -39,6 +40,33 @@ def init_github() -> Github:
39
40
  return Github(token, base_url=GH_BASE_URL)
40
41
 
41
42
 
43
+ def init_users_and_paths() -> list[dict[str, list]]:
44
+ app_int_users = queries.get_users(refs=True)
45
+
46
+ users = defaultdict(list)
47
+ for user in app_int_users:
48
+ u = user["org_username"]
49
+ item = PathSpec(type=PathTypes.USER, path=user["path"])
50
+ users[u].append(item)
51
+ for r in user.get("requests"):
52
+ item = PathSpec(type=PathTypes.REQUEST, path=r["path"])
53
+ users[u].append(item)
54
+ for q in user.get("queries"):
55
+ item = PathSpec(type=PathTypes.QUERY, path=q["path"])
56
+ users[u].append(item)
57
+ for g in user.get("gabi_instances"):
58
+ item = PathSpec(type=PathTypes.GABI, path=g["path"])
59
+ users[u].append(item)
60
+ for a in user.get("aws_accounts", []):
61
+ item = PathSpec(type=PathTypes.AWS_ACCOUNTS, path=a["path"])
62
+ users[u].append(item)
63
+ for s in user.get("schedules"):
64
+ item = PathSpec(type=PathTypes.SCHEDULE, path=s["path"])
65
+ users[u].append(item)
66
+
67
+ return [{"username": username, "paths": paths} for username, paths in users.items()]
68
+
69
+
42
70
  @retry(exceptions=(GithubException, ReadTimeout))
43
71
  def get_user_company(user: dict, github: Github) -> UserAndCompany:
44
72
  gh_user = github.get_user(login=user["github_username"])
@@ -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 LdapSettings {
23
+ settings: app_interface_settings_v1 {
24
+ ldap {
25
+ serverUrl
26
+ baseDn
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 LdapSettingsV1(ConfiguredBaseModel):
40
+ server_url: str = Field(..., alias="serverUrl")
41
+ base_dn: str = Field(..., alias="baseDn")
42
+
43
+
44
+ class AppInterfaceSettingsV1(ConfiguredBaseModel):
45
+ ldap: Optional[LdapSettingsV1] = Field(..., alias="ldap")
46
+
47
+
48
+ class LdapSettingsQueryData(ConfiguredBaseModel):
49
+ settings: Optional[list[AppInterfaceSettingsV1]] = Field(..., alias="settings")
50
+
51
+
52
+ def query(query_func: Callable, **kwargs: Any) -> LdapSettingsQueryData:
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
+ LdapSettingsQueryData: queried data parsed into generated classes
66
+ """
67
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
68
+ return LdapSettingsQueryData(**raw_data)
@@ -0,0 +1,102 @@
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 UsersWithPaths {
23
+ users: users_v1 {
24
+ path
25
+ org_username
26
+ aws_accounts {
27
+ path
28
+ }
29
+ requests {
30
+ path
31
+ }
32
+ queries {
33
+ path
34
+ }
35
+ gabi_instances {
36
+ path
37
+ }
38
+ schedules {
39
+ path
40
+ }
41
+ }
42
+ }
43
+ """
44
+
45
+
46
+ class ConfiguredBaseModel(BaseModel):
47
+ class Config:
48
+ smart_union=True
49
+ extra=Extra.forbid
50
+
51
+
52
+ class AWSAccountV1(ConfiguredBaseModel):
53
+ path: str = Field(..., alias="path")
54
+
55
+
56
+ class CredentialsRequestV1(ConfiguredBaseModel):
57
+ path: str = Field(..., alias="path")
58
+
59
+
60
+ class AppInterfaceSqlQueryV1(ConfiguredBaseModel):
61
+ path: str = Field(..., alias="path")
62
+
63
+
64
+ class GabiInstanceV1(ConfiguredBaseModel):
65
+ path: str = Field(..., alias="path")
66
+
67
+
68
+ class ScheduleV1(ConfiguredBaseModel):
69
+ path: str = Field(..., alias="path")
70
+
71
+
72
+ class UserV1(ConfiguredBaseModel):
73
+ path: str = Field(..., alias="path")
74
+ org_username: str = Field(..., alias="org_username")
75
+ aws_accounts: Optional[list[AWSAccountV1]] = Field(..., alias="aws_accounts")
76
+ requests: Optional[list[CredentialsRequestV1]] = Field(..., alias="requests")
77
+ queries: Optional[list[AppInterfaceSqlQueryV1]] = Field(..., alias="queries")
78
+ gabi_instances: Optional[list[GabiInstanceV1]] = Field(..., alias="gabi_instances")
79
+ schedules: Optional[list[ScheduleV1]] = Field(..., alias="schedules")
80
+
81
+
82
+ class UsersWithPathsQueryData(ConfiguredBaseModel):
83
+ users: Optional[list[UserV1]] = Field(..., alias="users")
84
+
85
+
86
+ def query(query_func: Callable, **kwargs: Any) -> UsersWithPathsQueryData:
87
+ """
88
+ This is a convenience function which queries and parses the data into
89
+ concrete types. It should be compatible with most GQL clients.
90
+ You do not have to use it to consume the generated data classes.
91
+ Alternatively, you can also mime and alternate the behavior
92
+ of this function in the caller.
93
+
94
+ Parameters:
95
+ query_func (Callable): Function which queries your GQL Server
96
+ kwargs: optional arguments that will be passed to the query function
97
+
98
+ Returns:
99
+ UsersWithPathsQueryData: queried data parsed into generated classes
100
+ """
101
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
102
+ return UsersWithPathsQueryData(**raw_data)
reconcile/ldap_users.py CHANGED
@@ -1,99 +1,126 @@
1
1
  import logging
2
- from collections import defaultdict
2
+ from collections.abc import Callable
3
+
4
+ from pydantic import BaseModel, Field
3
5
 
4
6
  from reconcile import (
5
7
  mr_client_gateway,
6
- queries,
7
8
  )
8
- from reconcile.utils import gql
9
+ from reconcile.gql_definitions.common.ldap_settings import LdapSettingsV1
10
+ from reconcile.gql_definitions.common.users_with_paths import UserV1
11
+ from reconcile.typed_queries.ldap_settings import get_ldap_settings
12
+ from reconcile.typed_queries.users_with_paths import get_users_with_paths
9
13
  from reconcile.utils.defer import defer
10
14
  from reconcile.utils.ldap_client import LdapClient
11
15
  from reconcile.utils.mr import (
12
16
  CreateDeleteUserAppInterface,
13
17
  CreateDeleteUserInfra,
14
18
  )
15
- from reconcile.utils.mr.user_maintenance import PathTypes
19
+ from reconcile.utils.mr.user_maintenance import PathSpec, PathTypes
16
20
 
17
21
  QONTRACT_INTEGRATION = "ldap-users"
18
22
 
19
23
 
20
- def init_users() -> list[dict[str, list]]:
21
- app_int_users = queries.get_users(refs=True)
22
-
23
- users = defaultdict(list)
24
- for user in app_int_users:
25
- u = user["org_username"]
26
- item = {"type": PathTypes.USER, "path": "data" + user["path"]}
27
- users[u].append(item)
28
- for r in user.get("requests"):
29
- item = {"type": PathTypes.REQUEST, "path": "data" + r["path"]}
30
- users[u].append(item)
31
- for q in user.get("queries"):
32
- item = {"type": PathTypes.QUERY, "path": "data" + q["path"]}
33
- users[u].append(item)
34
- for g in user.get("gabi_instances"):
35
- item = {"type": PathTypes.GABI, "path": "data" + g["path"]}
36
- users[u].append(item)
37
- for a in user.get("aws_accounts", []):
38
- item = {"type": PathTypes.AWS_ACCOUNTS, "path": "data" + a["path"]}
39
- users[u].append(item)
40
- for s in user.get("schedules"):
41
- item = {"type": PathTypes.SCHEDULE, "path": "data" + s["path"]}
42
- users[u].append(item)
43
-
44
- return [{"username": username, "paths": paths} for username, paths in users.items()]
45
-
46
-
47
- LDAP_SETTINGS_QUERY = """
48
- {
49
- settings: app_interface_settings_v1 {
50
- ldap {
51
- serverUrl
52
- baseDn
53
- }
54
- }
55
- }
56
- """
57
-
58
-
59
- def get_ldap_settings() -> dict:
60
- """Returns LDAP settings"""
61
- gqlapi = gql.get_api()
62
- settings = gqlapi.query(LDAP_SETTINGS_QUERY)["settings"]
63
- if settings:
64
- # assuming a single settings file for now
65
- return settings[0]
66
- raise ValueError("no app-interface-settings settings found")
24
+ class UserPaths(BaseModel):
25
+ username: str
26
+ paths: list[PathSpec] = Field(default_factory=list)
67
27
 
68
28
 
69
- @defer
70
- def run(dry_run, app_interface_project_id, infra_project_id, defer=None):
71
- users = init_users()
72
- with LdapClient.from_settings(get_ldap_settings()) as ldap_client:
73
- ldap_users = ldap_client.get_users([u["username"] for u in users])
29
+ def transform_users_with_paths(users_with_paths: list[UserV1]) -> list[UserPaths]:
30
+ """Converts UserV1 objects into UserPaths, consolidating all associated resource paths by type."""
31
+
32
+ users_paths = []
33
+ for user in users_with_paths:
34
+ up = UserPaths(username=user.org_username)
35
+ up.paths.append(PathSpec(type=PathTypes.USER, path=user.path))
36
+ for r in user.requests or []:
37
+ up.paths.append(PathSpec(type=PathTypes.REQUEST, path=r.path))
38
+ for q in user.queries or []:
39
+ up.paths.append(PathSpec(type=PathTypes.QUERY, path=q.path))
40
+ for g in user.gabi_instances or []:
41
+ up.paths.append(PathSpec(type=PathTypes.GABI, path=g.path))
42
+ for a in user.aws_accounts or []:
43
+ up.paths.append(PathSpec(type=PathTypes.AWS_ACCOUNTS, path=a.path))
44
+ for s in user.schedules or []:
45
+ up.paths.append(PathSpec(type=PathTypes.SCHEDULE, path=s.path))
46
+
47
+ users_paths.append(up)
48
+
49
+ return users_paths
50
+
51
+
52
+ def get_usernames(users_paths: list[UserPaths]) -> list[str]:
53
+ """Extracts a list of usernames from a list of UserPaths objects."""
54
+ return [u.username for u in users_paths]
55
+
56
+
57
+ def filter_users_not_exists(
58
+ users_paths: list[UserPaths], ldap_users: set[str]
59
+ ) -> list[UserPaths]:
60
+ """Filters out UserPaths objects whose usernames are not present in the given set of LDAP users."""
61
+
62
+ return [u for u in users_paths if u.username not in ldap_users]
63
+
74
64
 
75
- users_to_delete = [u for u in users if u["username"] not in ldap_users]
65
+ def get_ldap_users(ldap_settings: LdapSettingsV1, usernames: list[str]) -> set[str]:
66
+ """Retrieves existing usernames from LDAP based on the provided list and connection settings."""
67
+ with LdapClient.from_params(
68
+ server_url=ldap_settings.server_url,
69
+ user=None,
70
+ password=None,
71
+ base_dn=ldap_settings.base_dn,
72
+ ) as ldap_client:
73
+ return ldap_client.get_users(usernames)
76
74
 
75
+
76
+ @defer
77
+ def delete_user_from_app_interface(
78
+ dry_run: bool,
79
+ app_interface_project_id: str | int | None,
80
+ users: list[UserPaths],
81
+ defer: Callable | None = None,
82
+ ) -> None:
83
+ """Delete user data stored in the repository app-interface related with the given users."""
77
84
  if not dry_run:
78
85
  mr_cli_app_interface = mr_client_gateway.init(
79
86
  gitlab_project_id=app_interface_project_id, sqs_or_gitlab="gitlab"
80
87
  )
81
- defer(mr_cli_app_interface.cleanup)
82
- mr_cli_infra = mr_client_gateway.init(
83
- gitlab_project_id=infra_project_id, sqs_or_gitlab="gitlab"
84
- )
85
- defer(mr_cli_infra.cleanup)
88
+ if defer:
89
+ defer(mr_cli_app_interface.cleanup)
86
90
 
87
- for u in users_to_delete:
88
- username = u["username"]
89
- paths = u["paths"]
90
- logging.info(["delete_user", username])
91
+ for user in users:
92
+ logging.info(["delete_user", user.username])
91
93
 
92
94
  if not dry_run:
93
- mr = CreateDeleteUserAppInterface(username, paths)
95
+ mr = CreateDeleteUserAppInterface(user.username, user.paths)
94
96
  mr.submit(cli=mr_cli_app_interface)
95
97
 
98
+
99
+ def delete_user_from_infra(
100
+ dry_run: bool,
101
+ infra_project_id: str | int | None,
102
+ usernames: list[str],
103
+ ) -> None:
104
+ """Delete user data stored in the repository infra related with the given usernames."""
96
105
  if not dry_run:
97
- usernames = [u["username"] for u in users_to_delete]
98
- mr_infra = CreateDeleteUserInfra(usernames)
99
- mr_infra.submit(cli=mr_cli_infra)
106
+ with mr_client_gateway.init(
107
+ gitlab_project_id=infra_project_id, sqs_or_gitlab="gitlab"
108
+ ) as mr_cli_infra:
109
+ mr_infra = CreateDeleteUserInfra(usernames)
110
+ mr_infra.submit(cli=mr_cli_infra)
111
+
112
+
113
+ def run(dry_run: bool, app_interface_project_id: str, infra_project_id: str) -> None:
114
+ """
115
+ Synchronizes user data stored in the repositories app_interface and infra
116
+ associated with users that are no longer found in the LDAP.
117
+ """
118
+ users_paths = transform_users_with_paths(get_users_with_paths())
119
+
120
+ ldap_users = get_ldap_users(get_ldap_settings(), get_usernames(users_paths))
121
+
122
+ users_to_delete = filter_users_not_exists(users_paths, ldap_users)
123
+ usernames_to_delete = get_usernames(users_to_delete)
124
+
125
+ delete_user_from_app_interface(dry_run, app_interface_project_id, users_to_delete)
126
+ delete_user_from_infra(dry_run, infra_project_id, usernames_to_delete)
@@ -3,7 +3,7 @@ from abc import (
3
3
  abstractmethod,
4
4
  )
5
5
 
6
- from reconcile.ldap_users import get_ldap_settings
6
+ from reconcile.typed_queries.ldap_settings import get_ldap_settings
7
7
  from reconcile.utils.ldap_client import LdapClient
8
8
 
9
9
 
@@ -53,7 +53,10 @@ def init_ldap_group_member_provider(group_base_dn: str) -> LdapGroupMemberProvid
53
53
  settings = get_ldap_settings()
54
54
  return LdapGroupMemberProvider(
55
55
  LdapClient.from_params(
56
- settings["ldap"]["serverUrl"], None, None, settings["ldap"]["baseDn"]
56
+ server_url=settings.server_url,
57
+ user=None,
58
+ password=None,
59
+ base_dn=settings.base_dn,
57
60
  ),
58
61
  group_base_dn,
59
62
  )
@@ -0,0 +1,20 @@
1
+ from collections.abc import Callable
2
+
3
+ from reconcile.gql_definitions.common.ldap_settings import (
4
+ LdapSettingsV1,
5
+ query,
6
+ )
7
+ from reconcile.utils import gql
8
+ from reconcile.utils.exceptions import AppInterfaceSettingsError
9
+
10
+
11
+ def get_ldap_settings(
12
+ query_func: Callable | None = None,
13
+ ) -> LdapSettingsV1:
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 and data.settings[0].ldap:
19
+ return data.settings[0].ldap
20
+ raise AppInterfaceSettingsError("Ldap settings is not defined.")
@@ -0,0 +1,10 @@
1
+ from collections.abc import Callable
2
+
3
+ from reconcile.gql_definitions.common.users_with_paths import UserV1, query
4
+ from reconcile.utils import gql
5
+
6
+
7
+ def get_users_with_paths(query_func: Callable | None = None) -> list[UserV1]:
8
+ if not query_func:
9
+ query_func = gql.get_api().query
10
+ return query(query_func=query_func).users or []
@@ -56,13 +56,6 @@ class LdapClient:
56
56
 
57
57
  return dict(groups_and_members)
58
58
 
59
- @classmethod
60
- def from_settings(cls, settings: dict) -> "LdapClient":
61
- """Requires a nested dictionary with key 'ldap' in addition sub keys 'serverUrl' and 'baseDn'."""
62
- return LdapClient.from_params(
63
- settings["ldap"]["serverUrl"], None, None, settings["ldap"]["baseDn"]
64
- )
65
-
66
59
  @classmethod
67
60
  def from_params(
68
61
  cls, server_url: str, user: str | None, password: str | None, base_dn: str
@@ -1,5 +1,8 @@
1
+ from collections.abc import Iterable
2
+ from enum import Enum
1
3
  from pathlib import Path
2
4
 
5
+ from pydantic import BaseModel, validator
3
6
  from ruamel import yaml
4
7
 
5
8
  from reconcile.utils.gitlab_api import GitLabApi
@@ -7,7 +10,7 @@ from reconcile.utils.mr.base import MergeRequestBase
7
10
  from reconcile.utils.mr.labels import AUTO_MERGE
8
11
 
9
12
 
10
- class PathTypes:
13
+ class PathTypes(Enum):
11
14
  USER = 0
12
15
  REQUEST = 1
13
16
  QUERY = 2
@@ -16,10 +19,19 @@ class PathTypes:
16
19
  SCHEDULE = 5
17
20
 
18
21
 
22
+ class PathSpec(BaseModel):
23
+ type: PathTypes
24
+ path: str
25
+
26
+ @validator("path")
27
+ def prepend_data_to_path(cls, v):
28
+ return "data" + v
29
+
30
+
19
31
  class CreateDeleteUserAppInterface(MergeRequestBase):
20
32
  name = "create_delete_user_mr"
21
33
 
22
- def __init__(self, username, paths):
34
+ def __init__(self, username, paths: Iterable[PathSpec]):
23
35
  self.username = username
24
36
  self.paths = paths
25
37
 
@@ -37,8 +49,8 @@ class CreateDeleteUserAppInterface(MergeRequestBase):
37
49
 
38
50
  def process(self, gitlab_cli: GitLabApi) -> None:
39
51
  for path_spec in self.paths:
40
- path_type = path_spec["type"]
41
- path = path_spec["path"]
52
+ path_type = path_spec.type
53
+ path = path_spec.path
42
54
  if path_type in {PathTypes.USER, PathTypes.REQUEST, PathTypes.QUERY}:
43
55
  gitlab_cli.delete_file(
44
56
  branch_name=self.branch, file_path=path, commit_message=self.title