qontract-reconcile 0.10.2.dev28__py3-none-any.whl → 0.10.2.dev29__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.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/RECORD +15 -8
- {qontract_reconcile-0.10.2.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/entry_points.txt +2 -0
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/__init__.py +0 -0
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +86 -0
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +92 -0
- reconcile/gql_definitions/common/pipeline_providers.py +12 -0
- reconcile/utils/mr/app_sre_tekton_access_report.py +45 -0
- reconcile/utils/mr/glitchtip_access_reporter.py +6 -100
- reconcile/utils/mr/update_access_report_base.py +122 -0
- tools/app_sre_tekton_access_reporter.py +99 -0
- tools/app_sre_tekton_access_revalidation.py +90 -0
- tools/glitchtip_access_reporter.py +1 -3
- tools/qontract_cli.py +50 -0
- {qontract_reconcile-0.10.2.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/WHEEL +0 -0
{qontract_reconcile-0.10.2.dev28.dist-info → qontract_reconcile-0.10.2.dev29.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.dev29
|
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.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/RECORD
RENAMED
@@ -225,6 +225,9 @@ reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py,sha256=RpOrRY
|
|
225
225
|
reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py,sha256=zU-WJ9CASV1Ok-1jUro6K426v3ug5YNR1XoXmV7SwQ8,3364
|
226
226
|
reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
227
|
reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py,sha256=uVEEqU6YYmKsNTo6EWlFnoVmqha2rvBDx-wiD64VmG0,1679
|
228
|
+
reconcile/gql_definitions/app_sre_tekton_access_revalidation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
229
|
+
reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py,sha256=8Y4NsS5T7tumDWxY5MuoV50MK2i-DsLYSpCRjb7KaLE,2353
|
230
|
+
reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py,sha256=XdVxBxiyTR6Cy939EHNw__0k7iWrZWlhrgS5DakST0I,2504
|
228
231
|
reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
229
232
|
reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=JdqtE3gMpeodymPJST-aFVkYP_MO--_CcwjF070R5Cs,4883
|
230
233
|
reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -272,7 +275,7 @@ reconcile/gql_definitions/common/ocm_env_telemeter.py,sha256=jW0Q9WazDQVOxh4u0LM
|
|
272
275
|
reconcile/gql_definitions/common/ocm_environments.py,sha256=6-_4Bf6-wBWykNBxVAFYnDkgM8sSoATKdabakDR9ENs,2018
|
273
276
|
reconcile/gql_definitions/common/pagerduty_instances.py,sha256=CqHMNyI0O1knfzLJoDMmsa5cbVEppng6ae4OMYsvJFQ,2059
|
274
277
|
reconcile/gql_definitions/common/pgp_reencryption_settings.py,sha256=NPLmO6J-zSu5B9QiYbDezLHY3TuOO9ihRBV-Zr84R9w,2259
|
275
|
-
reconcile/gql_definitions/common/pipeline_providers.py,sha256=
|
278
|
+
reconcile/gql_definitions/common/pipeline_providers.py,sha256=9rpsqPuvj82B4ki56xHlBde0yvGFOdXMH0RDQkBRVx8,9394
|
276
279
|
reconcile/gql_definitions/common/quay_instances.py,sha256=toBkdYYVTmEafezAHZKgaW-mQ29xEW6jeronzsAlNyI,1786
|
277
280
|
reconcile/gql_definitions/common/quay_orgs.py,sha256=NhA8kqvVUDbrsryEvEL5mlIv5R3T4XNhSRXtfL_yptY,1788
|
278
281
|
reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
|
@@ -681,15 +684,17 @@ reconcile/utils/merge_request_manager/parser.py,sha256=5pGoz8Q6EuYXlUc1z-D0FahdR
|
|
681
684
|
reconcile/utils/mr/README.md,sha256=i9sCLkDFhSxAUtpa_I1_TxhR5vPOLcowuwn2VEWO41w,5794
|
682
685
|
reconcile/utils/mr/__init__.py,sha256=hcfHDIIIsJT4C0BnzDnyeZEfZdamrqHzMLcBzIT1ibI,2578
|
683
686
|
reconcile/utils/mr/app_interface_reporter.py,sha256=6Kpg93V9FvcOke9Jimkva359MQ-ZyBIkUpf8QIA6-to,1793
|
687
|
+
reconcile/utils/mr/app_sre_tekton_access_report.py,sha256=zSO_-d_5KA-wcb0uAx4WWIj_LjIKqozHS-I2leTOzRU,1508
|
684
688
|
reconcile/utils/mr/aws_access.py,sha256=9MMpYD24j2lLr_hLeMSh_OsJ07waalrlNpz-JlOsKAM,2575
|
685
689
|
reconcile/utils/mr/base.py,sha256=O8BWr6dibeQ22FDE9y56r6DK3UnC-5IhRXT7IWGrnxk,8069
|
686
690
|
reconcile/utils/mr/clusters_updates.py,sha256=pcusPAwRUkvyk_-bixsRNTzSvpTLypJ1kflq5UEVgcM,2271
|
687
|
-
reconcile/utils/mr/glitchtip_access_reporter.py,sha256=
|
691
|
+
reconcile/utils/mr/glitchtip_access_reporter.py,sha256=cTkOtzdgeKPaqro0VS2hDuAClQiN4nZATh-mplQC-AI,1369
|
688
692
|
reconcile/utils/mr/labels.py,sha256=9QRTRjZAtq45zELd9SwavaraczMjwjn5no3RK1YxFTg,825
|
689
693
|
reconcile/utils/mr/notificator.py,sha256=f8IcGQ1_iBsXJFnhPsWQ7UE3NfigaOrXcVieJPplYrY,2955
|
690
694
|
reconcile/utils/mr/ocm_update_recommended_version.py,sha256=p_aVP0TGrlKk9WBwgQnYWqUDsED_Hg6G5Bqj0UvtRwA,1536
|
691
695
|
reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py,sha256=5EncHGr4QRnZgHedRfCwMYZ9CaijYzHGj7-M6lhtQRo,3004
|
692
696
|
reconcile/utils/mr/promote_qontract.py,sha256=wgvX2CBlcZaihKJSXJ0zcEK8NGaEP2_DUQDz0STzGes,7158
|
697
|
+
reconcile/utils/mr/update_access_report_base.py,sha256=0vhF-eZTIjl7keBAOb2bO7LrlRAiticuUGh5EmI5MWc,4357
|
693
698
|
reconcile/utils/mr/user_maintenance.py,sha256=ZlR1Id_r2BUXsoerJW-0Ioh5bcbwlnQxBBhSs-ri9Dk,5099
|
694
699
|
reconcile/utils/ocm/__init__.py,sha256=Y-bp8GomMpyCo0tFW6kJ78-ZG1UIupYRtBzbMWU0kwM,798
|
695
700
|
reconcile/utils/ocm/addons.py,sha256=_LDdJ-gapM3s5exKlIUt-MlXZTAUoHezbYBU0QmvfWQ,7335
|
@@ -737,9 +742,11 @@ reconcile/utils/unleash/server.py,sha256=907gDh9Ee8UxLqusnfpzE-7LUnttB38D4xhVJ0v
|
|
737
742
|
tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
738
743
|
tools/app_interface_metrics_exporter.py,sha256=f1qwTmQfEcs98uBVRyBa0k7GQXdiSwd7w1hDVjhdGcQ,2303
|
739
744
|
tools/app_interface_reporter.py,sha256=gR2EgHmgSIxzK5xxDW1SduFU6OkPaf2LlAQjhV3NYIg,17623
|
740
|
-
tools/
|
745
|
+
tools/app_sre_tekton_access_reporter.py,sha256=o9prLUgQpwO3msRWc2as1xT1y9OB3znkpgvLr0Ys8_M,3146
|
746
|
+
tools/app_sre_tekton_access_revalidation.py,sha256=66nHEaY-bIqxIhpcmwN8AvQZu6ZXenfkg4Fut0pVZRM,2726
|
747
|
+
tools/glitchtip_access_reporter.py,sha256=o01A6b88t3Wie6tj_tJWWVo2J01LxQ_a9giGm4UzEaU,2901
|
741
748
|
tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
|
742
|
-
tools/qontract_cli.py,sha256=
|
749
|
+
tools/qontract_cli.py,sha256=76jUbYqgF_ViudSg4rAJCBCLrrQV5aR0nMSlZwH3MWU,147170
|
743
750
|
tools/sd_app_sre_alert_report.py,sha256=jQpJdXVID68bSNtJNOGDh0-ei1CfEUS4Itr4MAaBNFA,5062
|
744
751
|
tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
|
745
752
|
tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -766,7 +773,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
766
773
|
tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
|
767
774
|
tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
|
768
775
|
tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
|
769
|
-
qontract_reconcile-0.10.2.
|
770
|
-
qontract_reconcile-0.10.2.
|
771
|
-
qontract_reconcile-0.10.2.
|
772
|
-
qontract_reconcile-0.10.2.
|
776
|
+
qontract_reconcile-0.10.2.dev29.dist-info/METADATA,sha256=a6JAveCgr6HyV2-o-zIa88NmQXGd8ljE9r-SChKfa6o,24665
|
777
|
+
qontract_reconcile-0.10.2.dev29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
778
|
+
qontract_reconcile-0.10.2.dev29.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
|
779
|
+
qontract_reconcile-0.10.2.dev29.dist-info/RECORD,,
|
@@ -1,6 +1,8 @@
|
|
1
1
|
[console_scripts]
|
2
2
|
app-interface-metrics-exporter = tools.app_interface_metrics_exporter:main
|
3
3
|
app-interface-reporter = tools.app_interface_reporter:main
|
4
|
+
app-sre-tekton-access-reporter = tools.app_sre_tekton_access_reporter:main
|
5
|
+
app-sre-tekton-access-revalidation = tools.app_sre_tekton_access_revalidation:main
|
4
6
|
glitchtip-access-reporter = tools.glitchtip_access_reporter:main
|
5
7
|
glitchtip-access-revalidation = tools.glitchtip_access_revalidation:main
|
6
8
|
qontract-cli = tools.qontract_cli:root
|
File without changes
|
@@ -0,0 +1,86 @@
|
|
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 AppSRETektonAccessRevalidationRoles {
|
23
|
+
roles: roles_v1 {
|
24
|
+
path
|
25
|
+
access {
|
26
|
+
role
|
27
|
+
namespace {
|
28
|
+
path
|
29
|
+
}
|
30
|
+
}
|
31
|
+
users {
|
32
|
+
org_username
|
33
|
+
path
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
"""
|
38
|
+
|
39
|
+
|
40
|
+
class ConfiguredBaseModel(BaseModel):
|
41
|
+
class Config:
|
42
|
+
smart_union=True
|
43
|
+
extra=Extra.forbid
|
44
|
+
|
45
|
+
|
46
|
+
class NamespaceV1(ConfiguredBaseModel):
|
47
|
+
path: str = Field(..., alias="path")
|
48
|
+
|
49
|
+
|
50
|
+
class AccessV1(ConfiguredBaseModel):
|
51
|
+
role: Optional[str] = Field(..., alias="role")
|
52
|
+
namespace: Optional[NamespaceV1] = Field(..., alias="namespace")
|
53
|
+
|
54
|
+
|
55
|
+
class UserV1(ConfiguredBaseModel):
|
56
|
+
org_username: str = Field(..., alias="org_username")
|
57
|
+
path: str = Field(..., alias="path")
|
58
|
+
|
59
|
+
|
60
|
+
class RoleV1(ConfiguredBaseModel):
|
61
|
+
path: str = Field(..., alias="path")
|
62
|
+
access: Optional[list[AccessV1]] = Field(..., alias="access")
|
63
|
+
users: list[UserV1] = Field(..., alias="users")
|
64
|
+
|
65
|
+
|
66
|
+
class AppSRETektonAccessRevalidationRolesQueryData(ConfiguredBaseModel):
|
67
|
+
roles: Optional[list[RoleV1]] = Field(..., alias="roles")
|
68
|
+
|
69
|
+
|
70
|
+
def query(query_func: Callable, **kwargs: Any) -> AppSRETektonAccessRevalidationRolesQueryData:
|
71
|
+
"""
|
72
|
+
This is a convenience function which queries and parses the data into
|
73
|
+
concrete types. It should be compatible with most GQL clients.
|
74
|
+
You do not have to use it to consume the generated data classes.
|
75
|
+
Alternatively, you can also mime and alternate the behavior
|
76
|
+
of this function in the caller.
|
77
|
+
|
78
|
+
Parameters:
|
79
|
+
query_func (Callable): Function which queries your GQL Server
|
80
|
+
kwargs: optional arguments that will be passed to the query function
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
AppSRETektonAccessRevalidationRolesQueryData: queried data parsed into generated classes
|
84
|
+
"""
|
85
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
86
|
+
return AppSRETektonAccessRevalidationRolesQueryData(**raw_data)
|
@@ -0,0 +1,92 @@
|
|
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 AppSRETektonAccessRevalidationUsers {
|
23
|
+
users: users_v1 {
|
24
|
+
name
|
25
|
+
org_username
|
26
|
+
roles {
|
27
|
+
access {
|
28
|
+
role
|
29
|
+
namespace {
|
30
|
+
name
|
31
|
+
cluster {
|
32
|
+
name
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
"""
|
40
|
+
|
41
|
+
|
42
|
+
class ConfiguredBaseModel(BaseModel):
|
43
|
+
class Config:
|
44
|
+
smart_union=True
|
45
|
+
extra=Extra.forbid
|
46
|
+
|
47
|
+
|
48
|
+
class ClusterV1(ConfiguredBaseModel):
|
49
|
+
name: str = Field(..., alias="name")
|
50
|
+
|
51
|
+
|
52
|
+
class NamespaceV1(ConfiguredBaseModel):
|
53
|
+
name: str = Field(..., alias="name")
|
54
|
+
cluster: ClusterV1 = Field(..., alias="cluster")
|
55
|
+
|
56
|
+
|
57
|
+
class AccessV1(ConfiguredBaseModel):
|
58
|
+
role: Optional[str] = Field(..., alias="role")
|
59
|
+
namespace: Optional[NamespaceV1] = Field(..., alias="namespace")
|
60
|
+
|
61
|
+
|
62
|
+
class RoleV1(ConfiguredBaseModel):
|
63
|
+
access: Optional[list[AccessV1]] = Field(..., alias="access")
|
64
|
+
|
65
|
+
|
66
|
+
class UserV1(ConfiguredBaseModel):
|
67
|
+
name: str = Field(..., alias="name")
|
68
|
+
org_username: str = Field(..., alias="org_username")
|
69
|
+
roles: Optional[list[RoleV1]] = Field(..., alias="roles")
|
70
|
+
|
71
|
+
|
72
|
+
class AppSRETektonAccessRevalidationUsersQueryData(ConfiguredBaseModel):
|
73
|
+
users: Optional[list[UserV1]] = Field(..., alias="users")
|
74
|
+
|
75
|
+
|
76
|
+
def query(query_func: Callable, **kwargs: Any) -> AppSRETektonAccessRevalidationUsersQueryData:
|
77
|
+
"""
|
78
|
+
This is a convenience function which queries and parses the data into
|
79
|
+
concrete types. It should be compatible with most GQL clients.
|
80
|
+
You do not have to use it to consume the generated data classes.
|
81
|
+
Alternatively, you can also mime and alternate the behavior
|
82
|
+
of this function in the caller.
|
83
|
+
|
84
|
+
Parameters:
|
85
|
+
query_func (Callable): Function which queries your GQL Server
|
86
|
+
kwargs: optional arguments that will be passed to the query function
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
AppSRETektonAccessRevalidationUsersQueryData: queried data parsed into generated classes
|
90
|
+
"""
|
91
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
92
|
+
return AppSRETektonAccessRevalidationUsersQueryData(**raw_data)
|
@@ -95,7 +95,12 @@ query PipelineProviders {
|
|
95
95
|
}
|
96
96
|
namespace {
|
97
97
|
name
|
98
|
+
path
|
98
99
|
clusterAdmin
|
100
|
+
app {
|
101
|
+
name
|
102
|
+
path
|
103
|
+
}
|
99
104
|
cluster {
|
100
105
|
name
|
101
106
|
serverUrl
|
@@ -193,6 +198,11 @@ class PipelinesProviderTektonProviderDefaultsV1(ConfiguredBaseModel):
|
|
193
198
|
deploy_resources: Optional[DeployResourcesV1] = Field(..., alias="deployResources")
|
194
199
|
|
195
200
|
|
201
|
+
class AppV1(ConfiguredBaseModel):
|
202
|
+
name: str = Field(..., alias="name")
|
203
|
+
path: str = Field(..., alias="path")
|
204
|
+
|
205
|
+
|
196
206
|
class DisableClusterAutomationsV1(ConfiguredBaseModel):
|
197
207
|
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
198
208
|
|
@@ -210,7 +220,9 @@ class ClusterV1(ConfiguredBaseModel):
|
|
210
220
|
|
211
221
|
class NamespaceV1(ConfiguredBaseModel):
|
212
222
|
name: str = Field(..., alias="name")
|
223
|
+
path: str = Field(..., alias="path")
|
213
224
|
cluster_admin: Optional[bool] = Field(..., alias="clusterAdmin")
|
225
|
+
app: AppV1 = Field(..., alias="app")
|
214
226
|
cluster: ClusterV1 = Field(..., alias="cluster")
|
215
227
|
|
216
228
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
|
3
|
+
from reconcile.utils.mr.update_access_report_base import UpdateAccessReportBase
|
4
|
+
|
5
|
+
|
6
|
+
class UpdateAppSRETektonAccessReport(UpdateAccessReportBase):
|
7
|
+
name = "app_sre_tekton_access_report_mr"
|
8
|
+
short_description = "AppSRE Tekton Access Report"
|
9
|
+
template = """
|
10
|
+
| Username | Name | Apps with pipeline namespace access |
|
11
|
+
| -------- | ---- | ----------------------------------- |
|
12
|
+
{% for user in users|sort -%}
|
13
|
+
| {{ user.org_username }} | {{ user.name }} |{% for app in user.apps %} {{app}}{% if not loop.last%},{% endif %}{% endfor %} |
|
14
|
+
{% endfor %}
|
15
|
+
""".strip()
|
16
|
+
|
17
|
+
|
18
|
+
# User model
|
19
|
+
class AppSRETektonAccessReportUserModel(BaseModel):
|
20
|
+
org_username: str
|
21
|
+
name: str
|
22
|
+
apps: set[str]
|
23
|
+
|
24
|
+
def __lt__(self, other: object) -> bool:
|
25
|
+
if not isinstance(other, AppSRETektonAccessReportUserModel):
|
26
|
+
raise NotImplementedError(
|
27
|
+
"Cannot compare to non AppSRETektonAccessReportUser objects."
|
28
|
+
)
|
29
|
+
return self.org_username < other.org_username
|
30
|
+
|
31
|
+
|
32
|
+
# Mutable User class
|
33
|
+
class AppSRETektonAccessReportUser:
|
34
|
+
def __init__(self, name: str, org_username: str, apps: set):
|
35
|
+
self._org_username = org_username
|
36
|
+
self._name = name
|
37
|
+
self._apps = apps
|
38
|
+
|
39
|
+
def add_app(self, app: str) -> None:
|
40
|
+
self._apps.add(app)
|
41
|
+
|
42
|
+
def generate_model(self) -> AppSRETektonAccessReportUserModel:
|
43
|
+
return AppSRETektonAccessReportUserModel(
|
44
|
+
name=self._name, org_username=self._org_username, apps=self._apps
|
45
|
+
)
|
@@ -1,17 +1,12 @@
|
|
1
|
-
import logging
|
2
|
-
from collections.abc import Sequence
|
3
|
-
from datetime import UTC, date
|
4
|
-
from datetime import datetime as dt
|
5
|
-
from pathlib import Path
|
6
|
-
|
7
|
-
from jinja2 import Template
|
8
1
|
from pydantic import BaseModel
|
9
2
|
|
10
|
-
from reconcile.utils.
|
11
|
-
|
12
|
-
from reconcile.utils.mr.labels import AUTO_MERGE
|
3
|
+
from reconcile.utils.mr.update_access_report_base import UpdateAccessReportBase
|
4
|
+
|
13
5
|
|
14
|
-
|
6
|
+
class UpdateGlitchtipAccessReport(UpdateAccessReportBase):
|
7
|
+
name = "glitchtip_access_report_mr"
|
8
|
+
short_description = "glitchtip access report"
|
9
|
+
template = """
|
15
10
|
| Username | Name | Organizations and Access Levels |
|
16
11
|
| -------- | ---- | ------------------------------- |
|
17
12
|
{% for user in users|sort -%}
|
@@ -43,92 +38,3 @@ class GlitchtipAccessReportUser(BaseModel):
|
|
43
38
|
"Cannot compare to non GlitchtipAccessReportUser objects."
|
44
39
|
)
|
45
40
|
return self.username < other.username
|
46
|
-
|
47
|
-
|
48
|
-
class UpdateGlitchtipAccessReport(MergeRequestBase):
|
49
|
-
name = "glitchtip_access_report_mr"
|
50
|
-
|
51
|
-
def __init__(
|
52
|
-
self,
|
53
|
-
users: Sequence[GlitchtipAccessReportUser],
|
54
|
-
glitchtip_access_revalidation_workbook: Path,
|
55
|
-
dry_run: bool = True,
|
56
|
-
):
|
57
|
-
super().__init__()
|
58
|
-
self.labels = [AUTO_MERGE]
|
59
|
-
self._users = users
|
60
|
-
self._glitchtip_access_revalidation_workbook = str(
|
61
|
-
glitchtip_access_revalidation_workbook
|
62
|
-
)
|
63
|
-
self._isodate = dt.now(tz=UTC).isoformat()
|
64
|
-
self._dry_run = dry_run
|
65
|
-
|
66
|
-
@property
|
67
|
-
def title(self) -> str:
|
68
|
-
return f"[{self.name}] reports for {self._isodate}"
|
69
|
-
|
70
|
-
@property
|
71
|
-
def description(self) -> str:
|
72
|
-
return f"glitchtip access report for {self._isodate}"
|
73
|
-
|
74
|
-
def _render_current_users_table(self) -> str:
|
75
|
-
template = Template(CURRENT_USERS_TABLE_TEMPLATE, keep_trailing_newline=True)
|
76
|
-
return template.render(users=self._users)
|
77
|
-
|
78
|
-
def _render_tracking_table_row(self, old_number_of_users: int) -> str:
|
79
|
-
# | Date Reviewed | Number of Current Users | +/- Red Hat Users |
|
80
|
-
return f"| {date.today()} | {len(self._users)} | {len(self._users) - old_number_of_users} |\n"
|
81
|
-
|
82
|
-
def _update_workbook(self, workbook_md: str) -> str:
|
83
|
-
new_workbook_md = ""
|
84
|
-
number_of_skipped_lines = 0
|
85
|
-
skip = False
|
86
|
-
for line in workbook_md.splitlines():
|
87
|
-
if "<!-- current users table: start -->" in line:
|
88
|
-
# do not copy the old current users table
|
89
|
-
skip = True
|
90
|
-
# insert the new table including the marker
|
91
|
-
new_workbook_md += line + "\n"
|
92
|
-
new_workbook_md += self._render_current_users_table()
|
93
|
-
elif "<!-- current users table: end -->" in line:
|
94
|
-
skip = False
|
95
|
-
# insert the marker
|
96
|
-
new_workbook_md += line + "\n"
|
97
|
-
elif "<!-- tracking table: next row -->" in line:
|
98
|
-
# insert the new row including the marker
|
99
|
-
new_workbook_md += self._render_tracking_table_row(
|
100
|
-
old_number_of_users=number_of_skipped_lines - 2
|
101
|
-
if number_of_skipped_lines > 0
|
102
|
-
else 0
|
103
|
-
)
|
104
|
-
new_workbook_md += line + "\n"
|
105
|
-
elif not skip:
|
106
|
-
new_workbook_md += line + "\n"
|
107
|
-
else:
|
108
|
-
# count the number of skipped current users table lines
|
109
|
-
# this number minus the table header is the old number of users
|
110
|
-
number_of_skipped_lines += 1
|
111
|
-
|
112
|
-
return new_workbook_md
|
113
|
-
|
114
|
-
def process(self, gitlab_cli: GitLabApi) -> None:
|
115
|
-
workbook_md = gitlab_cli.project.files.get(
|
116
|
-
file_path=self._glitchtip_access_revalidation_workbook, ref=self.branch
|
117
|
-
)
|
118
|
-
workbook_md = self._update_workbook(workbook_md.decode().decode("utf-8"))
|
119
|
-
|
120
|
-
if not self._dry_run:
|
121
|
-
logging.info(
|
122
|
-
f"updating glitchtip access report: {self._glitchtip_access_revalidation_workbook}"
|
123
|
-
)
|
124
|
-
gitlab_cli.update_file(
|
125
|
-
branch_name=self.branch,
|
126
|
-
file_path=self._glitchtip_access_revalidation_workbook,
|
127
|
-
commit_message="update glitchtip access report",
|
128
|
-
content=workbook_md,
|
129
|
-
)
|
130
|
-
else:
|
131
|
-
logging.info(
|
132
|
-
f"dry-run: not updating glitchtip access report: {self._glitchtip_access_revalidation_workbook}"
|
133
|
-
)
|
134
|
-
logging.info(workbook_md)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import logging
|
2
|
+
from abc import abstractmethod
|
3
|
+
from collections.abc import Sequence
|
4
|
+
from datetime import UTC, date
|
5
|
+
from datetime import datetime as dt
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import TypeVar
|
8
|
+
|
9
|
+
from jinja2 import Template
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
from reconcile.utils.gitlab_api import GitLabApi
|
13
|
+
from reconcile.utils.mr.base import MergeRequestBase
|
14
|
+
from reconcile.utils.mr.labels import AUTO_MERGE
|
15
|
+
|
16
|
+
AccessReportUser = TypeVar("AccessReportUser", bound=BaseModel)
|
17
|
+
|
18
|
+
|
19
|
+
class UpdateAccessReportBase(MergeRequestBase):
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
users: Sequence[AccessReportUser],
|
23
|
+
workbook_path: Path,
|
24
|
+
dry_run: bool = True,
|
25
|
+
):
|
26
|
+
super().__init__()
|
27
|
+
self.labels = [AUTO_MERGE]
|
28
|
+
self._users = users
|
29
|
+
self._workbook_file_name = str(workbook_path)
|
30
|
+
self._isodate = dt.now(tz=UTC).isoformat()
|
31
|
+
self._dry_run = dry_run
|
32
|
+
|
33
|
+
@property
|
34
|
+
@abstractmethod
|
35
|
+
def short_description(self) -> str:
|
36
|
+
"""
|
37
|
+
Short Description of the Merge Request (without dates). It will be used to
|
38
|
+
build the Merge Request description as seen in the UI.
|
39
|
+
|
40
|
+
:return: Merge Request description as seen in the Gitlab Web UI without date.
|
41
|
+
:rtype: str
|
42
|
+
"""
|
43
|
+
|
44
|
+
@property
|
45
|
+
@abstractmethod
|
46
|
+
def template(self) -> str:
|
47
|
+
"""
|
48
|
+
Jinja2 template to generate the report main table.
|
49
|
+
|
50
|
+
:return: report jinja2 template.
|
51
|
+
:rtype: str
|
52
|
+
"""
|
53
|
+
|
54
|
+
@property
|
55
|
+
def title(self) -> str:
|
56
|
+
return f"[{self.name}] reports for {self._isodate}"
|
57
|
+
|
58
|
+
@property
|
59
|
+
def description(self) -> str:
|
60
|
+
return f"{self.short_description} for {self._isodate}"
|
61
|
+
|
62
|
+
def _render_current_users_table(self) -> str:
|
63
|
+
template = Template(self.template, keep_trailing_newline=True)
|
64
|
+
return template.render(users=self._users)
|
65
|
+
|
66
|
+
def _render_tracking_table_row(self, old_number_of_users: int) -> str:
|
67
|
+
# | Date Reviewed | Number of Current Users | +/- Red Hat Users |
|
68
|
+
return f"| {date.today()} | {len(self._users)} | {len(self._users) - old_number_of_users} |\n"
|
69
|
+
|
70
|
+
def _update_workbook(self, workbook_md: str) -> str:
|
71
|
+
new_workbook_md = ""
|
72
|
+
number_of_skipped_lines = 0
|
73
|
+
skip = False
|
74
|
+
for line in workbook_md.splitlines():
|
75
|
+
if "<!-- current users table: start -->" in line:
|
76
|
+
# do not copy the old current users table
|
77
|
+
skip = True
|
78
|
+
# insert the new table including the marker
|
79
|
+
new_workbook_md += line + "\n"
|
80
|
+
new_workbook_md += self._render_current_users_table()
|
81
|
+
elif "<!-- current users table: end -->" in line:
|
82
|
+
skip = False
|
83
|
+
# insert the marker
|
84
|
+
new_workbook_md += line + "\n"
|
85
|
+
elif "<!-- tracking table: next row -->" in line:
|
86
|
+
# insert the new row including the marker
|
87
|
+
new_workbook_md += self._render_tracking_table_row(
|
88
|
+
old_number_of_users=number_of_skipped_lines - 2
|
89
|
+
if number_of_skipped_lines > 0
|
90
|
+
else 0
|
91
|
+
)
|
92
|
+
new_workbook_md += line + "\n"
|
93
|
+
elif not skip:
|
94
|
+
new_workbook_md += line + "\n"
|
95
|
+
else:
|
96
|
+
# count the number of skipped current users table lines
|
97
|
+
# this number minus the table header is the old number of users
|
98
|
+
number_of_skipped_lines += 1
|
99
|
+
|
100
|
+
return new_workbook_md
|
101
|
+
|
102
|
+
def process(self, gitlab_cli: GitLabApi) -> None:
|
103
|
+
workbook_md = gitlab_cli.project.files.get(
|
104
|
+
file_path=self._workbook_file_name, ref=self.branch
|
105
|
+
)
|
106
|
+
workbook_md = self._update_workbook(workbook_md.decode().decode("utf-8"))
|
107
|
+
|
108
|
+
if not self._dry_run:
|
109
|
+
logging.info(
|
110
|
+
f"updating {self.short_description}: {self._workbook_file_name}"
|
111
|
+
)
|
112
|
+
gitlab_cli.update_file(
|
113
|
+
branch_name=self.branch,
|
114
|
+
file_path=self._workbook_file_name,
|
115
|
+
commit_message=f"update {self.short_description}",
|
116
|
+
content=workbook_md,
|
117
|
+
)
|
118
|
+
else:
|
119
|
+
logging.info(
|
120
|
+
f"dry-run: not updating {self.short_description}: {self._workbook_file_name}"
|
121
|
+
)
|
122
|
+
logging.info(workbook_md)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import logging
|
2
|
+
from collections import defaultdict
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import click
|
6
|
+
|
7
|
+
from reconcile import mr_client_gateway
|
8
|
+
from reconcile.cli import (
|
9
|
+
config_file,
|
10
|
+
dry_run,
|
11
|
+
gitlab_project_id,
|
12
|
+
log_level,
|
13
|
+
)
|
14
|
+
from reconcile.gql_definitions.app_sre_tekton_access_revalidation.users import (
|
15
|
+
query as users_query,
|
16
|
+
)
|
17
|
+
from reconcile.typed_queries.tekton_pipeline_providers import (
|
18
|
+
get_tekton_pipeline_providers,
|
19
|
+
)
|
20
|
+
from reconcile.utils import gql
|
21
|
+
from reconcile.utils.mr.app_sre_tekton_access_report import (
|
22
|
+
AppSRETektonAccessReportUser,
|
23
|
+
UpdateAppSRETektonAccessReport,
|
24
|
+
)
|
25
|
+
from reconcile.utils.runtime.environment import init_env
|
26
|
+
|
27
|
+
|
28
|
+
@click.command()
|
29
|
+
@config_file
|
30
|
+
@dry_run
|
31
|
+
@log_level
|
32
|
+
@gitlab_project_id
|
33
|
+
@click.option(
|
34
|
+
"--workbook-path",
|
35
|
+
help="path to AppSRE Tekton access revalidation workbook markdown file",
|
36
|
+
default="docs/app-sre/tekton/access-revalidation-workbook.md",
|
37
|
+
)
|
38
|
+
def main(
|
39
|
+
configfile: str,
|
40
|
+
dry_run: bool,
|
41
|
+
log_level: str,
|
42
|
+
gitlab_project_id: int,
|
43
|
+
workbook_path: str,
|
44
|
+
) -> None:
|
45
|
+
"""Update AppSRE Tekton access report.
|
46
|
+
|
47
|
+
This script updates the AppSRE Tekton access report (markdown file) with the latest
|
48
|
+
access information.
|
49
|
+
"""
|
50
|
+
|
51
|
+
init_env(log_level=log_level, config_file=configfile)
|
52
|
+
|
53
|
+
# pipeline providers namespaces dict, containing the all the pipelines namespaces
|
54
|
+
# the (cluster_name, namespace_name) tuple to app_name correspondence.
|
55
|
+
pp_namespaces_apps = {
|
56
|
+
(p.namespace.cluster.name, p.namespace.name): p.namespace.app.name
|
57
|
+
for p in get_tekton_pipeline_providers()
|
58
|
+
}
|
59
|
+
|
60
|
+
report_users: dict[str, AppSRETektonAccessReportUser] = {}
|
61
|
+
users = users_query(query_func=gql.get_api().query).users or []
|
62
|
+
for u in users:
|
63
|
+
namespace_roles = defaultdict(set)
|
64
|
+
for r in u.roles or []:
|
65
|
+
if r.access is None:
|
66
|
+
continue
|
67
|
+
|
68
|
+
for a in r.access:
|
69
|
+
if a.namespace is None:
|
70
|
+
continue
|
71
|
+
|
72
|
+
namespace_tuple = (a.namespace.cluster.name, a.namespace.name)
|
73
|
+
namespace_roles[namespace_tuple].add(a.role)
|
74
|
+
|
75
|
+
for namespace_tuple, roles in namespace_roles.items():
|
76
|
+
if pp_app := pp_namespaces_apps.get(namespace_tuple):
|
77
|
+
if "tekton-trigger-access" in roles or "view" in roles:
|
78
|
+
if ru := report_users.get(u.org_username):
|
79
|
+
ru.add_app(pp_app)
|
80
|
+
else:
|
81
|
+
report_users[u.org_username] = AppSRETektonAccessReportUser(
|
82
|
+
name=u.name, org_username=u.org_username, apps={pp_app}
|
83
|
+
)
|
84
|
+
|
85
|
+
mr = UpdateAppSRETektonAccessReport(
|
86
|
+
users=[u.generate_model() for u in report_users.values()],
|
87
|
+
workbook_path=Path(workbook_path),
|
88
|
+
dry_run=dry_run,
|
89
|
+
)
|
90
|
+
with mr_client_gateway.init(
|
91
|
+
gitlab_project_id=gitlab_project_id, sqs_or_gitlab="gitlab"
|
92
|
+
) as mr_cli:
|
93
|
+
result = mr.submit(cli=mr_cli)
|
94
|
+
if result:
|
95
|
+
logging.info(["created_mr", result.web_url])
|
96
|
+
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
main() # pylint: disable=no-value-for-parameter
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import logging
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import click
|
5
|
+
|
6
|
+
from reconcile import mr_client_gateway
|
7
|
+
from reconcile.cli import (
|
8
|
+
config_file,
|
9
|
+
dry_run,
|
10
|
+
gitlab_project_id,
|
11
|
+
log_level,
|
12
|
+
)
|
13
|
+
from reconcile.typed_queries.tekton_pipeline_providers import (
|
14
|
+
get_tekton_pipeline_providers,
|
15
|
+
)
|
16
|
+
from reconcile.utils.mr.labels import AUTO_MERGE
|
17
|
+
from reconcile.utils.mr.notificator import (
|
18
|
+
CreateAppInterfaceNotificator,
|
19
|
+
Notification,
|
20
|
+
)
|
21
|
+
from reconcile.utils.runtime.environment import init_env
|
22
|
+
|
23
|
+
EMAIL_BODY = """Hello app-interface service owner,
|
24
|
+
|
25
|
+
Access to all Tekton pipelines namespaces must be revalidated regularly. This ensures
|
26
|
+
that the access is still valid and is needed to safeguard against unauthorized access.
|
27
|
+
|
28
|
+
Please review, within one week, that all your app-interface roles that grant access to
|
29
|
+
those namespaces are assigned to the appropriate users. In order to help you identifying
|
30
|
+
those roles and users, please take a look into the app-interface documentation:
|
31
|
+
https://gitlab.cee.redhat.com/service/app-interface/-/blob/master/docs/app-sre/tekton/access-revalidation.md
|
32
|
+
|
33
|
+
If you have questions about this, please post a question in the #sd-app-sre Slack channel.
|
34
|
+
|
35
|
+
Thank you,
|
36
|
+
|
37
|
+
The AppSRE team
|
38
|
+
"""
|
39
|
+
|
40
|
+
|
41
|
+
@click.command()
|
42
|
+
@config_file
|
43
|
+
@dry_run
|
44
|
+
@log_level
|
45
|
+
@gitlab_project_id
|
46
|
+
@click.option(
|
47
|
+
"--email-dir",
|
48
|
+
help="app-interface dir to store new AppSRE Tekton revalidation emails",
|
49
|
+
default="data/app-interface/emails/app-sre-tekton",
|
50
|
+
)
|
51
|
+
def main(
|
52
|
+
configfile: str,
|
53
|
+
dry_run: bool,
|
54
|
+
log_level: str,
|
55
|
+
gitlab_project_id: int,
|
56
|
+
email_dir: str,
|
57
|
+
) -> None:
|
58
|
+
"""Revalidate Glitchtip access.
|
59
|
+
|
60
|
+
This script sends an email (via MR) to all app-interface service owners (apps)
|
61
|
+
that have a pipelines provider associated to the application. The email asks the
|
62
|
+
service owners to revalidate the access to the pipelines providers namespaces.
|
63
|
+
"""
|
64
|
+
init_env(log_level=log_level, config_file=configfile)
|
65
|
+
|
66
|
+
apps = {p.namespace.app.path for p in get_tekton_pipeline_providers()}
|
67
|
+
notification = Notification(
|
68
|
+
notification_type="Action Required",
|
69
|
+
short_description="AppSRE Tekton Access Revalidation",
|
70
|
+
description=EMAIL_BODY,
|
71
|
+
services=list(apps),
|
72
|
+
recipients=[],
|
73
|
+
)
|
74
|
+
mr = CreateAppInterfaceNotificator(
|
75
|
+
notification,
|
76
|
+
labels=[AUTO_MERGE],
|
77
|
+
email_base_path=Path(email_dir),
|
78
|
+
dry_run=dry_run,
|
79
|
+
)
|
80
|
+
|
81
|
+
with mr_client_gateway.init(
|
82
|
+
gitlab_project_id=gitlab_project_id, sqs_or_gitlab="gitlab"
|
83
|
+
) as mr_cli:
|
84
|
+
result = mr.submit(cli=mr_cli)
|
85
|
+
if result:
|
86
|
+
logging.info(["created_mr", result.web_url])
|
87
|
+
|
88
|
+
|
89
|
+
if __name__ == "__main__":
|
90
|
+
main() # pylint: disable=no-value-for-parameter
|
@@ -77,9 +77,7 @@ def main(
|
|
77
77
|
|
78
78
|
mr = UpdateGlitchtipAccessReport(
|
79
79
|
users=list(users.values()),
|
80
|
-
|
81
|
-
glitchtip_access_revalidation_workbook_path
|
82
|
-
),
|
80
|
+
workbook_path=Path(glitchtip_access_revalidation_workbook_path),
|
83
81
|
dry_run=dry_run,
|
84
82
|
)
|
85
83
|
with mr_client_gateway.init(
|
tools/qontract_cli.py
CHANGED
@@ -75,6 +75,9 @@ from reconcile.cli import (
|
|
75
75
|
from reconcile.gql_definitions.advanced_upgrade_service.aus_clusters import (
|
76
76
|
query as aus_clusters_query,
|
77
77
|
)
|
78
|
+
from reconcile.gql_definitions.app_sre_tekton_access_revalidation.roles import (
|
79
|
+
query as app_sre_tekton_access_revalidation_roles_query,
|
80
|
+
)
|
78
81
|
from reconcile.gql_definitions.common.app_interface_vault_settings import (
|
79
82
|
AppInterfaceSettingsV1,
|
80
83
|
)
|
@@ -96,6 +99,9 @@ from reconcile.typed_queries.clusters import get_clusters
|
|
96
99
|
from reconcile.typed_queries.saas_files import get_saas_files
|
97
100
|
from reconcile.typed_queries.slo_documents import get_slo_documents
|
98
101
|
from reconcile.typed_queries.status_board import get_status_board
|
102
|
+
from reconcile.typed_queries.tekton_pipeline_providers import (
|
103
|
+
get_tekton_pipeline_providers,
|
104
|
+
)
|
99
105
|
from reconcile.utils import (
|
100
106
|
amtool,
|
101
107
|
config,
|
@@ -4460,5 +4466,49 @@ You can view the source of this Markdown to extract the JSON data.
|
|
4460
4466
|
print_output(ctx.obj["options"], results, columns)
|
4461
4467
|
|
4462
4468
|
|
4469
|
+
@get.command(help="Get all app tekton pipelines providers roles and users")
|
4470
|
+
@click.argument("app-name")
|
4471
|
+
@click.pass_context
|
4472
|
+
def tekton_roles_and_users(ctx, app_name):
|
4473
|
+
pp_namespaces = {
|
4474
|
+
p.namespace.path
|
4475
|
+
for p in get_tekton_pipeline_providers()
|
4476
|
+
if p.namespace.app.name == app_name
|
4477
|
+
}
|
4478
|
+
|
4479
|
+
roles = (
|
4480
|
+
app_sre_tekton_access_revalidation_roles_query(
|
4481
|
+
query_func=gql.get_api().query
|
4482
|
+
).roles
|
4483
|
+
or []
|
4484
|
+
)
|
4485
|
+
columns = ["namespace_path", "role_path", "users"]
|
4486
|
+
results = []
|
4487
|
+
for r in roles:
|
4488
|
+
if r.access is None:
|
4489
|
+
continue
|
4490
|
+
|
4491
|
+
seen = False # to avoid printing a namespace more than once
|
4492
|
+
for a in r.access:
|
4493
|
+
if a.namespace is None:
|
4494
|
+
continue
|
4495
|
+
if a.namespace.path in pp_namespaces:
|
4496
|
+
if not seen:
|
4497
|
+
seen = True
|
4498
|
+
|
4499
|
+
if ctx.obj["options"]["output"] == "table":
|
4500
|
+
users = ", ".join([u.org_username for u in r.users])
|
4501
|
+
else:
|
4502
|
+
users = [u.path for u in r.users]
|
4503
|
+
|
4504
|
+
results.append({
|
4505
|
+
"namespace_path": a.namespace.path,
|
4506
|
+
"role_path": r.path,
|
4507
|
+
"users": users,
|
4508
|
+
})
|
4509
|
+
|
4510
|
+
print_output(ctx.obj["options"], results, columns)
|
4511
|
+
|
4512
|
+
|
4463
4513
|
if __name__ == "__main__":
|
4464
4514
|
root() # pylint: disable=no-value-for-parameter
|
{qontract_reconcile-0.10.2.dev28.dist-info → qontract_reconcile-0.10.2.dev29.dist-info}/WHEEL
RENAMED
File without changes
|