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.
- {qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/RECORD +13 -9
- reconcile/github_users.py +29 -1
- reconcile/gql_definitions/common/ldap_settings.py +68 -0
- reconcile/gql_definitions/common/users_with_paths.py +102 -0
- reconcile/ldap_users.py +97 -70
- reconcile/oum/providers.py +5 -2
- reconcile/typed_queries/ldap_settings.py +20 -0
- reconcile/typed_queries/users_with_paths.py +10 -0
- reconcile/utils/ldap_client.py +0 -7
- reconcile/utils/mr/user_maintenance.py +16 -4
- {qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
3
|
+
Version: 0.10.2.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
|
{qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
809
|
-
qontract_reconcile-0.10.2.
|
810
|
-
qontract_reconcile-0.10.2.
|
811
|
-
qontract_reconcile-0.10.2.
|
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
|
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.
|
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
|
-
|
21
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
82
|
-
|
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
|
88
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
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)
|
reconcile/oum/providers.py
CHANGED
@@ -3,7 +3,7 @@ from abc import (
|
|
3
3
|
abstractmethod,
|
4
4
|
)
|
5
5
|
|
6
|
-
from reconcile.
|
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
|
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 []
|
reconcile/utils/ldap_client.py
CHANGED
@@ -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
|
41
|
-
path = path_spec
|
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
|
{qontract_reconcile-0.10.2.dev186.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|