qontract-reconcile 0.10.2.dev185__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.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/METADATA +1 -2
- {qontract_reconcile-0.10.2.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/RECORD +16 -14
- reconcile/acs_policies.py +1 -13
- reconcile/cli.py +0 -11
- reconcile/github_users.py +29 -1
- reconcile/gql_definitions/acs/acs_policies.py +0 -90
- 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
- reconcile/acs_notifiers.py +0 -126
- reconcile/utils/acs/notifiers.py +0 -145
- {qontract_reconcile-0.10.2.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev185.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
|
@@ -99,7 +99,6 @@ OpenShift templates can be found [here](/openshift/qontract-reconcile.yaml). In
|
|
99
99
|
`qontract-reconcile` includes the following integrations:
|
100
100
|
|
101
101
|
```text
|
102
|
-
acs-notifiers Manages RHACS notifier configurations
|
103
102
|
acs-policies Manages RHACS security policy configurations
|
104
103
|
acs-rbac Manages RHACS rbac configuration
|
105
104
|
advanced-upgrade-scheduler Manage Cluster Upgrade Policy schedules in
|
{qontract_reconcile-0.10.2.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/RECORD
RENAMED
@@ -1,6 +1,5 @@
|
|
1
1
|
reconcile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
reconcile/
|
3
|
-
reconcile/acs_policies.py,sha256=xNbhIlwE1u2URbEQcX-3C-pTu--XjrKAqGj0-Wd85dY,9152
|
2
|
+
reconcile/acs_policies.py,sha256=pwFKP3afmRbpRq-7FRAosI-A60yfufE2vvXBjOMgsCU,8653
|
4
3
|
reconcile/acs_rbac.py,sha256=15vNfNzdG_DeXaJ-f5m8DSaJh__LUK766_xAECqyTsg,22657
|
5
4
|
reconcile/aws_ami_share.py,sha256=M_gT7y3cSAyT_Pm90PBCNDSmbZtqREqe2jNETh0i9Qs,3808
|
6
5
|
reconcile/aws_ecr_image_pull_secrets.py,sha256=F58PtX1GlB9XHqj8hGy9ItiTznXLAAKTNlWD9iT2MWI,2593
|
@@ -10,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=O0JX2N5kNRKs3u2xzu4NNrI6p0ag5JWy3MTsv
|
|
10
9
|
reconcile/aws_support_cases_sos.py,sha256=PDhilxQ4TBxVnxUPIUdTbKEaNUI0wzPiEsB91oHT2fY,3384
|
11
10
|
reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
|
12
11
|
reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
|
13
|
-
reconcile/cli.py,sha256
|
12
|
+
reconcile/cli.py,sha256=-WTtuEtxDuHdZ-GIGtklu4p6r8YdR7QfRqdK6rHl2gY,108163
|
14
13
|
reconcile/closedbox_endpoint_monitoring_base.py,sha256=al7m8EgnnYx90rY1REryW3byN_ItfJfAzEeLtjbCfi0,4921
|
15
14
|
reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
|
16
15
|
reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
|
@@ -27,7 +26,7 @@ reconcile/github_org.py,sha256=Wc5cZamatuWsW2ZJT2ib5ps8l3iY3RXHwNUxVJerqz0,14173
|
|
27
26
|
reconcile/github_owners.py,sha256=viE1KJ-zaTxuZ5yItg2C263J0brn-Q-3hR_DkYDMbhY,3122
|
28
27
|
reconcile/github_repo_invites.py,sha256=U9UCzNVwrZ7MqODtFah8ogH0NNY-XjBin7G9gqHtCUY,2690
|
29
28
|
reconcile/github_repo_permissions_validator.py,sha256=PNqL4dqa2OaNBy-NmLVN-t1HZa6eS6HgSYmfOunYqtA,1798
|
30
|
-
reconcile/github_users.py,sha256=
|
29
|
+
reconcile/github_users.py,sha256=ZeYNMyvZPVMx6mh5TiKEUQy4W1uw3VmUZOHiXus5I0Y,5073
|
31
30
|
reconcile/github_validator.py,sha256=-j17tn3csFVjPMSPL3te48iWVkPZCncRXdeKeLdGjjQ,931
|
32
31
|
reconcile/gitlab_fork_compliance.py,sha256=RbHckzLnE9zkOFHJANzoejEMMbMAivmqJVs3Suvp9lU,4591
|
33
32
|
reconcile/gitlab_housekeeping.py,sha256=c31Jtw5t8bnOzUO9jMWF_0DHitPzol93AA7YWBxM5L0,25416
|
@@ -48,7 +47,7 @@ reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffF
|
|
48
47
|
reconcile/jenkins_worker_fleets.py,sha256=L2wEXpd4xuEHrXGss4iH788nG8UlLSYduZe1EY2IVw4,5377
|
49
48
|
reconcile/jira_permissions_validator.py,sha256=5rc4Q2mXGL3HCZmYpZaJkjzBrpCRnlLeCY0Yl2fDOs4,14672
|
50
49
|
reconcile/jira_watcher.py,sha256=L_UL2MKm2SoIGNsCLThm28pnqCkoFc154JWsD6bURug,3593
|
51
|
-
reconcile/ldap_users.py,sha256=
|
50
|
+
reconcile/ldap_users.py,sha256=oP1CAxmgSi3zDJ3vKTPySjap6WmEX1U469FmFrov5l4,4599
|
52
51
|
reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
|
53
52
|
reconcile/ocm_additional_routers.py,sha256=KfcFDVbNoc6n5dHWjYdAf1_DiVqVG6Tw23WLKoV8cdg,3306
|
54
53
|
reconcile/ocm_addons.py,sha256=qqAyqRBRbdZQvAcjb-QlSVyRAyQBZk6iVlgnI4jyi7s,3353
|
@@ -230,7 +229,7 @@ reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
230
229
|
reconcile/gql_definitions/introspection.json,sha256=MT1HCR90ZahZxjrgaBEp34JNhjaEiDRblQK-qfSrfF0,2316277
|
231
230
|
reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
232
231
|
reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
|
233
|
-
reconcile/gql_definitions/acs/acs_policies.py,sha256=
|
232
|
+
reconcile/gql_definitions/acs/acs_policies.py,sha256=Ygpfl2-VkYLSlJvHgp_dJBfb66K_Rwfdfpsa18w1v1s,4338
|
234
233
|
reconcile/gql_definitions/acs/acs_rbac.py,sha256=cZsIlCWliPQdQHgmBsIMx54fJNOtkdRXLzmOKZmJNHk,3009
|
235
234
|
reconcile/gql_definitions/advanced_upgrade_service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
236
235
|
reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py,sha256=230uwBoBaaVz686d_WNZT9eAw6BX2VpoQX0mKhf5UcM,4390
|
@@ -285,6 +284,7 @@ reconcile/gql_definitions/common/clusters_with_peering.py,sha256=B1Hi3u6rZZsl4bD
|
|
285
284
|
reconcile/gql_definitions/common/github_orgs.py,sha256=rZ0pDAA2_9hF9N-ykRZIxPtEmczTSjuA_k3nkp0k1W0,2039
|
286
285
|
reconcile/gql_definitions/common/jira_settings.py,sha256=Fmjxhlhr69kc4jkG_0k17fuYlQVucbNex0jXYu83wbY,1990
|
287
286
|
reconcile/gql_definitions/common/jiralert_settings.py,sha256=H96nMg_r2YcOvioj3aIkwqtFrALGSLt7uhbx9jGSUTo,1984
|
287
|
+
reconcile/gql_definitions/common/ldap_settings.py,sha256=qkKm3BusR4FreHcI9VSwPg-hrhBKtiQIjGsTAGvHDG4,1885
|
288
288
|
reconcile/gql_definitions/common/namespaces.py,sha256=FUgyoDAKWSetfDumfqHRVT6lOGp9hoj1-8rC0khVJI4,11071
|
289
289
|
reconcile/gql_definitions/common/namespaces_minimal.py,sha256=XVt8LFe-bGYbjN3ysX3b9sFGmLX4snQ_A9ZouQGaaAI,3429
|
290
290
|
reconcile/gql_definitions/common/ocm_env_telemeter.py,sha256=jW0Q9WazDQVOxh4u0LMFG69rupBioJ8HGGjvR9bVK9Y,2424
|
@@ -302,6 +302,7 @@ reconcile/gql_definitions/common/slack_workspaces.py,sha256=2o0kgi4QiaRuNmZJnc_B
|
|
302
302
|
reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
|
303
303
|
reconcile/gql_definitions/common/state_aws_account.py,sha256=LAdpCG2-ykVpWBPO0Zu1WvG-hwKXyDC0fJQxJRpbqCk,2198
|
304
304
|
reconcile/gql_definitions/common/users.py,sha256=ahY3d185LbTekCGYBLJwZJljn54RJI_P5CVefdqyoZA,1705
|
305
|
+
reconcile/gql_definitions/common/users_with_paths.py,sha256=kOYzIXKG11JI0afgRMA5WLlEwJlvElAKytE8RAeuSco,2726
|
305
306
|
reconcile/gql_definitions/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
306
307
|
reconcile/gql_definitions/cost_report/app_names.py,sha256=fzqYXyiTSll359J1F1o7qapco0MSxgs3sr_Ssb2Kbns,1786
|
307
308
|
reconcile/gql_definitions/cost_report/cost_namespaces.py,sha256=URRozAgSa9OnkqOCZf3MGH21_wcnsqYl0n-olXdjQH0,2286
|
@@ -466,7 +467,7 @@ reconcile/oum/base.py,sha256=WCFdHOHXLPrJcvxVqw6HjaJthT7olC5BQqqXlD4DM6c,13552
|
|
466
467
|
reconcile/oum/labelset.py,sha256=f5kDndbaIT4iNYxTRPSELTUgj_aMlzEJDPzooAkG2mE,2154
|
467
468
|
reconcile/oum/metrics.py,sha256=S_0C-hIW4jHVl9Lltgis9q-p33fdBjADWBouQ9Emeao,1575
|
468
469
|
reconcile/oum/models.py,sha256=teH0bJTCMTzbdbYD9CU4yXDuMr34ceLcM0KuoIPU8gI,1712
|
469
|
-
reconcile/oum/providers.py,sha256=
|
470
|
+
reconcile/oum/providers.py,sha256=lfG6d7YV-A4Lte45EMv1Gx4A346piJ_jAkrU5AHJZ_g,1834
|
470
471
|
reconcile/oum/standalone.py,sha256=EN5y1S-3DwUZYzSRqRMtf63mI2slvBHKiU9zOTjYvWM,7334
|
471
472
|
reconcile/prometheus_rules_tester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
472
473
|
reconcile/prometheus_rules_tester/integration.py,sha256=8lXZSaNqZAemulVNDxanrxwl7ZGfUxtwfptJlMPX8ac,9308
|
@@ -562,6 +563,7 @@ reconcile/typed_queries/jenkins.py,sha256=Pus8Rhsb04F92Iqh31xe-rW1TLiEziIWd0UAth
|
|
562
563
|
reconcile/typed_queries/jira.py,sha256=jq6-ERCr_Fh96_3i9A9UKfpAAstoc4Iz6Irl-_0IUkw,235
|
563
564
|
reconcile/typed_queries/jira_settings.py,sha256=i0ddx5xxHrM1v-9mtL_6OB-jBFLw7-HS6xenpIDjrkw,570
|
564
565
|
reconcile/typed_queries/jiralert_settings.py,sha256=y59S5xvYmuaGxszzfKhVLjbCyDwKiaSIlajocbK5MDE,793
|
566
|
+
reconcile/typed_queries/ldap_settings.py,sha256=k8LWM11wPHn6_x2TMP7xkiwQTOPBgzuYdjzygG1WVT4,664
|
565
567
|
reconcile/typed_queries/namespaces.py,sha256=vItPrn7sfcHOix-VvkzQkf54_ljzI_ymyxh5esdBJ5Y,262
|
566
568
|
reconcile/typed_queries/namespaces_minimal.py,sha256=rUtqNQ0ORXXUTQfnpsMURymAJ4gYtE77V-Lb3LiJFEY,278
|
567
569
|
reconcile/typed_queries/ocm.py,sha256=aTXW9NaMpMq-90sBUAUQmGPtk6Hnsk2rzSbXv3pD8dY,312
|
@@ -578,6 +580,7 @@ reconcile/typed_queries/tekton_pipeline_providers.py,sha256=LtoSnSRkuckYsXIU64L1
|
|
578
580
|
reconcile/typed_queries/terraform_namespaces.py,sha256=4H9WE90jN_BVYBAt1DxJITS4vkL-vykbXZIS1H4EKNM,413
|
579
581
|
reconcile/typed_queries/unleash.py,sha256=7HDc4owF044xM9Thx4WsXV7DZgETxJjy4lbpwmqz1vU,282
|
580
582
|
reconcile/typed_queries/users.py,sha256=UXlaxeZAoNIugMEndfcjbkHYowUURE72aWcdmxfb3yk,377
|
583
|
+
reconcile/typed_queries/users_with_paths.py,sha256=lvW0QzTJtKkLS2O_Jm9_0mFXJFKvGlZE809LI-5ddvc,342
|
581
584
|
reconcile/typed_queries/vault.py,sha256=lkRsmobykorof3fcrIPLz-NgvAiSOWSOZc_jXBlnl1w,274
|
582
585
|
reconcile/typed_queries/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
583
586
|
reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py,sha256=X-N1WJGOL6OR9940P0_K4-YJzkL5Vg4favhYrBxXD9A,327
|
@@ -630,7 +633,7 @@ reconcile/utils/jjb_client.py,sha256=e5cDeNAeJMGz3sZMJ1KUIMFyLdRet0YnC0Qgj1vTPHc
|
|
630
633
|
reconcile/utils/jsonpath.py,sha256=wdxOMqR-GMpQf5vRPWRMqAF7bCiXDBkkcFfY2U4j_tk,5536
|
631
634
|
reconcile/utils/jump_host.py,sha256=gi8vGUDgdTVwJvROvRVauFxtL0YAramhbWvG70L7AY8,5137
|
632
635
|
reconcile/utils/keycloak.py,sha256=YWSEUGrOVqFaJUk055dKUWpLDPdDRvhcmvR-lfbmxdE,3388
|
633
|
-
reconcile/utils/ldap_client.py,sha256=
|
636
|
+
reconcile/utils/ldap_client.py,sha256=HxOxpqyLRbXxOARZyBNbccceWIk6gF8PQjNQmBvjF2o,2148
|
634
637
|
reconcile/utils/lean_terraform_client.py,sha256=X9358loxzkhwRExTeDv_NC8Q6HNr2tJK6Lx897YtJUc,4004
|
635
638
|
reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
|
636
639
|
reconcile/utils/metrics.py,sha256=kiOoWO0b0mO-MDZWxyClYz9SeohQ0QU-xji0p-cSiLo,18462
|
@@ -675,7 +678,6 @@ reconcile/utils/vaultsecretref.py,sha256=0KUSzuvTRxPyKY919TO3-B_eYg4_76fzKvMF8j5
|
|
675
678
|
reconcile/utils/vcs.py,sha256=AK35vIjx9bXYclKmvNekpaG_OETt-ZybibwV-m123xc,10186
|
676
679
|
reconcile/utils/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
677
680
|
reconcile/utils/acs/base.py,sha256=4UsDrCpAOuddL3PKNuIQYoJP1BtZQNNB8_KEX0lXneg,2532
|
678
|
-
reconcile/utils/acs/notifiers.py,sha256=DlzTDM9arWQlBSiDy70y5Mf38OKVs9V0FzFe2LfOKXA,5046
|
679
681
|
reconcile/utils/acs/policies.py,sha256=jpbi3qpGkBD_X6MfzsX12dPajUbmACmhIOz_0rDvYzs,5489
|
680
682
|
reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
|
681
683
|
reconcile/utils/aws_api_typed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -729,7 +731,7 @@ reconcile/utils/mr/ocm_update_recommended_version.py,sha256=p_aVP0TGrlKk9WBwgQnY
|
|
729
731
|
reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py,sha256=5EncHGr4QRnZgHedRfCwMYZ9CaijYzHGj7-M6lhtQRo,3004
|
730
732
|
reconcile/utils/mr/promote_qontract.py,sha256=wgvX2CBlcZaihKJSXJ0zcEK8NGaEP2_DUQDz0STzGes,7158
|
731
733
|
reconcile/utils/mr/update_access_report_base.py,sha256=4Grohtp44v1sSHZyIAYOwClxH8SLj_nnOrCcHPKp9p0,4361
|
732
|
-
reconcile/utils/mr/user_maintenance.py,sha256=
|
734
|
+
reconcile/utils/mr/user_maintenance.py,sha256=KSfl4i0k1CqCa9mj93bvFxHeBHPRMn3sBWdGS1nNzYY,5371
|
733
735
|
reconcile/utils/ocm/__init__.py,sha256=Y-bp8GomMpyCo0tFW6kJ78-ZG1UIupYRtBzbMWU0kwM,798
|
734
736
|
reconcile/utils/ocm/addons.py,sha256=_LDdJ-gapM3s5exKlIUt-MlXZTAUoHezbYBU0QmvfWQ,7335
|
735
737
|
reconcile/utils/ocm/base.py,sha256=8rZ8WilNeAfq7HRNI8kNOLB4VcYzQpqQ5gvWbS54MuM,14576
|
@@ -807,7 +809,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
807
809
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
|
808
810
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
809
811
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
810
|
-
qontract_reconcile-0.10.2.
|
811
|
-
qontract_reconcile-0.10.2.
|
812
|
-
qontract_reconcile-0.10.2.
|
813
|
-
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/acs_policies.py
CHANGED
@@ -8,7 +8,6 @@ from reconcile.gql_definitions.acs.acs_policies import (
|
|
8
8
|
AcsPolicyV1,
|
9
9
|
)
|
10
10
|
from reconcile.utils import gql
|
11
|
-
from reconcile.utils.acs.notifiers import JiraNotifier
|
12
11
|
from reconcile.utils.acs.policies import AcsPolicyApi, Policy, PolicyCondition, Scope
|
13
12
|
from reconcile.utils.differ import diff_iterables
|
14
13
|
from reconcile.utils.runtime.integration import (
|
@@ -63,21 +62,10 @@ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
|
|
63
62
|
conditions = [
|
64
63
|
pc for c in gql_policy.conditions if (pc := self._build_policy_condition(c))
|
65
64
|
]
|
66
|
-
jira_notifier = (
|
67
|
-
notifier_name_to_id.get(
|
68
|
-
JiraNotifier.from_escalation_policy(
|
69
|
-
gql_policy.integrations.notifiers.jira.escalation_policy
|
70
|
-
).name
|
71
|
-
)
|
72
|
-
if gql_policy.integrations
|
73
|
-
and gql_policy.integrations.notifiers
|
74
|
-
and gql_policy.integrations.notifiers.jira
|
75
|
-
else None
|
76
|
-
)
|
77
65
|
return Policy(
|
78
66
|
name=gql_policy.name,
|
79
67
|
description=gql_policy.description,
|
80
|
-
notifiers=[
|
68
|
+
notifiers=[],
|
81
69
|
severity=f"{gql_policy.severity.upper()}_SEVERITY", # align with acs api severity value format
|
82
70
|
scope=sorted(
|
83
71
|
[
|
reconcile/cli.py
CHANGED
@@ -3719,17 +3719,6 @@ def acs_policies(ctx):
|
|
3719
3719
|
)
|
3720
3720
|
|
3721
3721
|
|
3722
|
-
@integration.command(short_help="Manages RHACS notifier configurations")
|
3723
|
-
@click.pass_context
|
3724
|
-
def acs_notifiers(ctx):
|
3725
|
-
from reconcile import acs_notifiers
|
3726
|
-
|
3727
|
-
run_class_integration(
|
3728
|
-
integration=acs_notifiers.AcsNotifiersIntegration(),
|
3729
|
-
ctx=ctx.obj,
|
3730
|
-
)
|
3731
|
-
|
3732
|
-
|
3733
3722
|
@integration.command(short_help="Manage Unleash feature toggles.")
|
3734
3723
|
@click.option("--instance", help="Reconcile just this Unlash instance.", default=None)
|
3735
3724
|
@click.pass_context
|
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"])
|
@@ -24,40 +24,6 @@ query AcsPolicy {
|
|
24
24
|
name
|
25
25
|
description
|
26
26
|
severity
|
27
|
-
integrations {
|
28
|
-
notifiers {
|
29
|
-
jira {
|
30
|
-
escalationPolicy {
|
31
|
-
name
|
32
|
-
channels {
|
33
|
-
jiraBoard {
|
34
|
-
name
|
35
|
-
server {
|
36
|
-
serverUrl
|
37
|
-
}
|
38
|
-
severityPriorityMappings {
|
39
|
-
name
|
40
|
-
mappings {
|
41
|
-
severity
|
42
|
-
priority
|
43
|
-
}
|
44
|
-
}
|
45
|
-
issueType
|
46
|
-
issueFields {
|
47
|
-
name
|
48
|
-
value
|
49
|
-
}
|
50
|
-
disable {
|
51
|
-
integrations
|
52
|
-
}
|
53
|
-
}
|
54
|
-
jiraComponent
|
55
|
-
jiraLabels
|
56
|
-
}
|
57
|
-
}
|
58
|
-
}
|
59
|
-
}
|
60
|
-
}
|
61
27
|
categories
|
62
28
|
scope {
|
63
29
|
level
|
@@ -107,61 +73,6 @@ class ConfiguredBaseModel(BaseModel):
|
|
107
73
|
extra=Extra.forbid
|
108
74
|
|
109
75
|
|
110
|
-
class JiraServerV1(ConfiguredBaseModel):
|
111
|
-
server_url: str = Field(..., alias="serverUrl")
|
112
|
-
|
113
|
-
|
114
|
-
class SeverityPriorityMappingV1(ConfiguredBaseModel):
|
115
|
-
severity: str = Field(..., alias="severity")
|
116
|
-
priority: str = Field(..., alias="priority")
|
117
|
-
|
118
|
-
|
119
|
-
class JiraSeverityPriorityMappingsV1(ConfiguredBaseModel):
|
120
|
-
name: str = Field(..., alias="name")
|
121
|
-
mappings: list[SeverityPriorityMappingV1] = Field(..., alias="mappings")
|
122
|
-
|
123
|
-
|
124
|
-
class JiraBoardIssueFieldV1(ConfiguredBaseModel):
|
125
|
-
name: str = Field(..., alias="name")
|
126
|
-
value: str = Field(..., alias="value")
|
127
|
-
|
128
|
-
|
129
|
-
class DisableJiraBoardAutomationsV1(ConfiguredBaseModel):
|
130
|
-
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
131
|
-
|
132
|
-
|
133
|
-
class JiraBoardV1(ConfiguredBaseModel):
|
134
|
-
name: str = Field(..., alias="name")
|
135
|
-
server: JiraServerV1 = Field(..., alias="server")
|
136
|
-
severity_priority_mappings: JiraSeverityPriorityMappingsV1 = Field(..., alias="severityPriorityMappings")
|
137
|
-
issue_type: Optional[str] = Field(..., alias="issueType")
|
138
|
-
issue_fields: Optional[list[JiraBoardIssueFieldV1]] = Field(..., alias="issueFields")
|
139
|
-
disable: Optional[DisableJiraBoardAutomationsV1] = Field(..., alias="disable")
|
140
|
-
|
141
|
-
|
142
|
-
class AppEscalationPolicyChannelsV1(ConfiguredBaseModel):
|
143
|
-
jira_board: list[JiraBoardV1] = Field(..., alias="jiraBoard")
|
144
|
-
jira_component: Optional[str] = Field(..., alias="jiraComponent")
|
145
|
-
jira_labels: Optional[list[str]] = Field(..., alias="jiraLabels")
|
146
|
-
|
147
|
-
|
148
|
-
class AppEscalationPolicyV1(ConfiguredBaseModel):
|
149
|
-
name: str = Field(..., alias="name")
|
150
|
-
channels: AppEscalationPolicyChannelsV1 = Field(..., alias="channels")
|
151
|
-
|
152
|
-
|
153
|
-
class AcsPolicyIntegrationNotifierJiraV1(ConfiguredBaseModel):
|
154
|
-
escalation_policy: AppEscalationPolicyV1 = Field(..., alias="escalationPolicy")
|
155
|
-
|
156
|
-
|
157
|
-
class AcsPolicyIntegrationNotifiersV1(ConfiguredBaseModel):
|
158
|
-
jira: Optional[AcsPolicyIntegrationNotifierJiraV1] = Field(..., alias="jira")
|
159
|
-
|
160
|
-
|
161
|
-
class AcsPolicyIntegrationsV1(ConfiguredBaseModel):
|
162
|
-
notifiers: Optional[AcsPolicyIntegrationNotifiersV1] = Field(..., alias="notifiers")
|
163
|
-
|
164
|
-
|
165
76
|
class AcsPolicyScopeV1(ConfiguredBaseModel):
|
166
77
|
level: str = Field(..., alias="level")
|
167
78
|
|
@@ -218,7 +129,6 @@ class AcsPolicyV1(ConfiguredBaseModel):
|
|
218
129
|
name: str = Field(..., alias="name")
|
219
130
|
description: Optional[str] = Field(..., alias="description")
|
220
131
|
severity: str = Field(..., alias="severity")
|
221
|
-
integrations: Optional[AcsPolicyIntegrationsV1] = Field(..., alias="integrations")
|
222
132
|
categories: list[str] = Field(..., alias="categories")
|
223
133
|
scope: Union[AcsPolicyScopeClusterV1, AcsPolicyScopeNamespaceV1, AcsPolicyScopeV1] = Field(..., alias="scope")
|
224
134
|
conditions: list[Union[AcsPolicyConditionsCvssV1, AcsPolicyConditionsSeverityV1, AcsPolicyConditionsImageTagV1, AcsPolicyConditionsCveV1, AcsPolicyConditionsImageAgeV1, AcsPolicyConditionsV1]] = Field(..., alias="conditions")
|
@@ -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
|
reconcile/acs_notifiers.py
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
import reconcile.gql_definitions.acs.acs_policies as gql_acs_policies
|
4
|
-
from reconcile.gql_definitions.jira.jira_servers import (
|
5
|
-
JiraServerV1,
|
6
|
-
)
|
7
|
-
from reconcile.gql_definitions.jira.jira_servers import (
|
8
|
-
query as query_jira_servers,
|
9
|
-
)
|
10
|
-
from reconcile.utils import gql
|
11
|
-
from reconcile.utils.acs.notifiers import (
|
12
|
-
AcsNotifiersApi,
|
13
|
-
JiraCredentials,
|
14
|
-
JiraNotifier,
|
15
|
-
)
|
16
|
-
from reconcile.utils.differ import diff_iterables
|
17
|
-
from reconcile.utils.disabled_integrations import integration_is_enabled
|
18
|
-
from reconcile.utils.runtime.integration import (
|
19
|
-
NoParams,
|
20
|
-
QontractReconcileIntegration,
|
21
|
-
)
|
22
|
-
from reconcile.utils.semver_helper import make_semver
|
23
|
-
|
24
|
-
|
25
|
-
class AcsNotifiersIntegration(QontractReconcileIntegration[NoParams]):
|
26
|
-
def __init__(self) -> None:
|
27
|
-
super().__init__(NoParams())
|
28
|
-
self.qontract_integration = "acs-notifiers"
|
29
|
-
self.qontract_integration_version = make_semver(0, 1, 0)
|
30
|
-
|
31
|
-
@property
|
32
|
-
def name(self) -> str:
|
33
|
-
return self.qontract_integration
|
34
|
-
|
35
|
-
def _get_escalation_policies(
|
36
|
-
self, acs_policies: list[gql_acs_policies.AcsPolicyV1]
|
37
|
-
) -> list[gql_acs_policies.AppEscalationPolicyV1]:
|
38
|
-
return list(
|
39
|
-
{
|
40
|
-
p.integrations.notifiers.jira.escalation_policy.name: p.integrations.notifiers.jira.escalation_policy
|
41
|
-
for p in acs_policies
|
42
|
-
if p.integrations
|
43
|
-
and p.integrations.notifiers
|
44
|
-
and p.integrations.notifiers.jira
|
45
|
-
and integration_is_enabled(
|
46
|
-
self.qontract_integration,
|
47
|
-
p.integrations.notifiers.jira.escalation_policy.channels.jira_board[
|
48
|
-
0
|
49
|
-
],
|
50
|
-
)
|
51
|
-
}.values()
|
52
|
-
)
|
53
|
-
|
54
|
-
def get_desired_state(
|
55
|
-
self, acs_policies: list[gql_acs_policies.AcsPolicyV1]
|
56
|
-
) -> list[JiraNotifier]:
|
57
|
-
return [
|
58
|
-
JiraNotifier.from_escalation_policy(ep)
|
59
|
-
for ep in self._get_escalation_policies(acs_policies)
|
60
|
-
]
|
61
|
-
|
62
|
-
def get_jira_credentials(
|
63
|
-
self, jira_servers: list[JiraServerV1]
|
64
|
-
) -> dict[str, JiraCredentials]:
|
65
|
-
return {
|
66
|
-
server.server_url: JiraCredentials(
|
67
|
-
url=server.server_url,
|
68
|
-
username=server.username,
|
69
|
-
token=self.secret_reader.read_secret(server.token),
|
70
|
-
)
|
71
|
-
for server in jira_servers
|
72
|
-
}
|
73
|
-
|
74
|
-
def reconcile(
|
75
|
-
self,
|
76
|
-
current_state: list[JiraNotifier],
|
77
|
-
desired_state: list[JiraNotifier],
|
78
|
-
acs_api: AcsNotifiersApi,
|
79
|
-
jira_credentials: dict[str, JiraCredentials],
|
80
|
-
dry_run: bool,
|
81
|
-
) -> None:
|
82
|
-
diff = diff_iterables(
|
83
|
-
current=current_state, desired=desired_state, key=lambda x: x.name
|
84
|
-
)
|
85
|
-
for a in diff.add.values():
|
86
|
-
logging.info(f"Create Jira notifier: {a.name}")
|
87
|
-
if not dry_run:
|
88
|
-
acs_api.create_jira_notifier(
|
89
|
-
a,
|
90
|
-
jira_credentials=jira_credentials[a.url],
|
91
|
-
)
|
92
|
-
for c in diff.change.values():
|
93
|
-
logging.info(f"Update Jira notifier: {c.desired.name}")
|
94
|
-
if not dry_run:
|
95
|
-
acs_api.update_jira_notifier(
|
96
|
-
c.desired,
|
97
|
-
jira_credentials=jira_credentials[c.desired.url],
|
98
|
-
)
|
99
|
-
for d in diff.delete.values():
|
100
|
-
logging.info(f"Delete Jira notifier: {d.name}")
|
101
|
-
if not dry_run:
|
102
|
-
acs_api.delete_jira_notifier(d)
|
103
|
-
|
104
|
-
def run(
|
105
|
-
self,
|
106
|
-
dry_run: bool,
|
107
|
-
) -> None:
|
108
|
-
gql_api_query = gql.get_api().query
|
109
|
-
jira_credentials = self.get_jira_credentials(
|
110
|
-
query_jira_servers(query_func=gql_api_query).jira_servers or []
|
111
|
-
)
|
112
|
-
desired_state = self.get_desired_state(
|
113
|
-
gql_acs_policies.query(query_func=gql_api_query).acs_policies or []
|
114
|
-
)
|
115
|
-
instance = AcsNotifiersApi.get_acs_instance(query_func=gql_api_query)
|
116
|
-
with AcsNotifiersApi(
|
117
|
-
url=instance.url, token=self.secret_reader.read_secret(instance.credentials)
|
118
|
-
) as acs_api:
|
119
|
-
current_state = acs_api.get_jira_notifiers()
|
120
|
-
self.reconcile(
|
121
|
-
current_state=current_state,
|
122
|
-
desired_state=desired_state,
|
123
|
-
acs_api=acs_api,
|
124
|
-
jira_credentials=jira_credentials,
|
125
|
-
dry_run=dry_run,
|
126
|
-
)
|
reconcile/utils/acs/notifiers.py
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from typing import Any
|
3
|
-
|
4
|
-
from pydantic import BaseModel
|
5
|
-
|
6
|
-
from reconcile.gql_definitions.acs.acs_policies import AppEscalationPolicyV1
|
7
|
-
from reconcile.utils.acs.base import AcsBaseApi
|
8
|
-
|
9
|
-
|
10
|
-
class JiraCredentials(BaseModel):
|
11
|
-
url: str
|
12
|
-
username: str
|
13
|
-
token: str
|
14
|
-
|
15
|
-
|
16
|
-
class SeverityPriorityMapping(BaseModel):
|
17
|
-
severity: str
|
18
|
-
priority: str
|
19
|
-
|
20
|
-
@staticmethod
|
21
|
-
def from_api(mapping: dict[str, str]) -> "SeverityPriorityMapping":
|
22
|
-
return SeverityPriorityMapping(
|
23
|
-
severity=mapping["severity"].replace("_SEVERITY", "").lower(),
|
24
|
-
priority=mapping["priorityName"],
|
25
|
-
)
|
26
|
-
|
27
|
-
def to_api(self) -> Any:
|
28
|
-
return {
|
29
|
-
"severity": f"{self.severity.upper()}_SEVERITY",
|
30
|
-
"priorityName": self.priority,
|
31
|
-
}
|
32
|
-
|
33
|
-
|
34
|
-
class JiraNotifier(BaseModel):
|
35
|
-
name: str
|
36
|
-
board: str
|
37
|
-
url: str
|
38
|
-
issue_type: str | None
|
39
|
-
severity_priority_mappings: list[SeverityPriorityMapping]
|
40
|
-
custom_fields: dict[str, Any] | None
|
41
|
-
|
42
|
-
@staticmethod
|
43
|
-
def from_api(notifier: dict[str, Any]) -> "JiraNotifier":
|
44
|
-
notifier_jira = notifier["jira"]
|
45
|
-
return JiraNotifier(
|
46
|
-
name=notifier["name"],
|
47
|
-
board=notifier["labelDefault"],
|
48
|
-
url=notifier_jira["url"],
|
49
|
-
issue_type=notifier_jira["issueType"],
|
50
|
-
severity_priority_mappings=sorted(
|
51
|
-
[
|
52
|
-
SeverityPriorityMapping.from_api(mapping)
|
53
|
-
for mapping in notifier_jira["priorityMappings"]
|
54
|
-
],
|
55
|
-
key=lambda m: m.severity,
|
56
|
-
),
|
57
|
-
custom_fields=json.loads(notifier_jira.get("defaultFieldsJson") or "{}"),
|
58
|
-
)
|
59
|
-
|
60
|
-
def to_api(self, ui_endpoint: str, jira_credentials: JiraCredentials) -> Any:
|
61
|
-
return {
|
62
|
-
"name": self.name,
|
63
|
-
"type": "jira",
|
64
|
-
"uiEndpoint": ui_endpoint,
|
65
|
-
"labelDefault": self.board,
|
66
|
-
"jira": {
|
67
|
-
"url": jira_credentials.url,
|
68
|
-
"username": jira_credentials.username,
|
69
|
-
"password": jira_credentials.token,
|
70
|
-
"issueType": self.issue_type or "Task",
|
71
|
-
"priorityMappings": [
|
72
|
-
mapping.to_api() for mapping in self.severity_priority_mappings
|
73
|
-
],
|
74
|
-
"defaultFieldsJson": json.dumps(self.custom_fields or {}),
|
75
|
-
},
|
76
|
-
}
|
77
|
-
|
78
|
-
@staticmethod
|
79
|
-
def from_escalation_policy(
|
80
|
-
escalation_policy: AppEscalationPolicyV1,
|
81
|
-
) -> "JiraNotifier":
|
82
|
-
jira_board = escalation_policy.channels.jira_board[0]
|
83
|
-
|
84
|
-
custom_fields: dict[str, Any] = {}
|
85
|
-
for field in jira_board.issue_fields or []:
|
86
|
-
if field.name == "Security Level":
|
87
|
-
custom_fields["security"] = {"name": field.value}
|
88
|
-
|
89
|
-
if escalation_policy.channels.jira_component:
|
90
|
-
custom_fields["components"] = [
|
91
|
-
{"name": escalation_policy.channels.jira_component}
|
92
|
-
]
|
93
|
-
if escalation_policy.channels.jira_labels:
|
94
|
-
custom_fields["labels"] = escalation_policy.channels.jira_labels
|
95
|
-
|
96
|
-
return JiraNotifier(
|
97
|
-
name=f"jira-{escalation_policy.name}",
|
98
|
-
board=jira_board.name,
|
99
|
-
url=jira_board.server.server_url,
|
100
|
-
issue_type=jira_board.issue_type or "Task",
|
101
|
-
severity_priority_mappings=sorted(
|
102
|
-
[
|
103
|
-
SeverityPriorityMapping(**vars(sp))
|
104
|
-
for sp in jira_board.severity_priority_mappings.mappings
|
105
|
-
],
|
106
|
-
key=lambda m: m.severity,
|
107
|
-
),
|
108
|
-
custom_fields=custom_fields,
|
109
|
-
)
|
110
|
-
|
111
|
-
|
112
|
-
class AcsNotifiersApi(AcsBaseApi):
|
113
|
-
"""
|
114
|
-
Implements methods to support reconcile operations against the ACS NotifiersService api
|
115
|
-
"""
|
116
|
-
|
117
|
-
def get_notifiers(self) -> list[Any]:
|
118
|
-
return self.generic_request_json("/v1/notifiers", "GET")["notifiers"]
|
119
|
-
|
120
|
-
def get_jira_notifiers(self) -> list[JiraNotifier]:
|
121
|
-
return [
|
122
|
-
JiraNotifier.from_api(notifier)
|
123
|
-
for notifier in self.get_notifiers()
|
124
|
-
if notifier["type"] == "jira"
|
125
|
-
]
|
126
|
-
|
127
|
-
def get_notifier_id_by_name(self, name: str) -> str:
|
128
|
-
return next(n["id"] for n in self.get_notifiers() if n["name"] == name)
|
129
|
-
|
130
|
-
def update_jira_notifier(
|
131
|
-
self, jira_notifier: JiraNotifier, jira_credentials: JiraCredentials
|
132
|
-
) -> None:
|
133
|
-
notifier_id = self.get_notifier_id_by_name(jira_notifier.name)
|
134
|
-
body = jira_notifier.to_api(self.url, jira_credentials)
|
135
|
-
self.generic_request(f"/v1/notifiers/{notifier_id}", "PUT", body)
|
136
|
-
|
137
|
-
def create_jira_notifier(
|
138
|
-
self, jira_notifier: JiraNotifier, jira_credentials: JiraCredentials
|
139
|
-
) -> None:
|
140
|
-
body = jira_notifier.to_api(self.url, jira_credentials)
|
141
|
-
self.generic_request("/v1/notifiers", "POST", body)
|
142
|
-
|
143
|
-
def delete_jira_notifier(self, jira_notifier: JiraNotifier) -> None:
|
144
|
-
notifier_id = self.get_notifier_id_by_name(jira_notifier.name)
|
145
|
-
self.generic_request(f"/v1/notifiers/{notifier_id}", "DELETE")
|
{qontract_reconcile-0.10.2.dev185.dist-info → qontract_reconcile-0.10.2.dev187.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|