qontract-reconcile 0.10.2.dev27__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev27
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
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=q96mwr2KeEQ5bpNniGlgIMZTxiuLSodcYfX-t
10
10
  reconcile/aws_support_cases_sos.py,sha256=hl_9L53yQYRQxKs3IWrd69Cc60XK067g_bJRM9B0udo,2975
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
12
12
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
13
- reconcile/cli.py,sha256=BCfKBc5BRq-9Y5WYe8sUoz9ufOsB1WwMvylx6mBf6CQ,107806
13
+ reconcile/cli.py,sha256=vHCmBcriMEqwVDj_7wujQFbrYa4b6yiiFTYCByWkbwg,107407
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=MvGKBqH9PdHWdMjhLuptze-dk0Tifhp3-0SZdI-7Fmo,4862
15
15
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
16
16
  reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
@@ -46,7 +46,7 @@ reconcile/jenkins_roles.py,sha256=HadmoNhgOoKMFZJmCe_Gdg0_a9k_1MuOXidtr801nUU,45
46
46
  reconcile/jenkins_webhooks.py,sha256=dzMT1ywXjeAo5sHj-ittW06Ed3beAUPjnc_oCAtD-Rg,2150
47
47
  reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffFnDGau9aY,1539
48
48
  reconcile/jenkins_worker_fleets.py,sha256=L2wEXpd4xuEHrXGss4iH788nG8UlLSYduZe1EY2IVw4,5377
49
- reconcile/jira_permissions_validator.py,sha256=uuwzizb16jTWGctDB0cG0t9l3trDxZuK2SJA-9w7tfA,14872
49
+ reconcile/jira_permissions_validator.py,sha256=DCKUyaqwUcIN4BuNtdREQW44K9yFxa1RUuA8a-dzXFE,13336
50
50
  reconcile/jira_watcher.py,sha256=L_UL2MKm2SoIGNsCLThm28pnqCkoFc154JWsD6bURug,3593
51
51
  reconcile/ldap_users.py,sha256=7hdO5CAPl-VNBvDRmKHg13LoblHXXPt7YEKNGomAoGg,3158
52
52
  reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
@@ -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=JJgmmghqLIwjKOdcWYHPnf4PDgAq4GF7046i0ozrqgI,9127
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=i5vo9jjBifX5wIcLwEMH5_VFVg-NY-pBKe0HysSw4CQ,5031
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/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
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=T637u3EVpodta2SSIjMa-3doLqXci1AEDt3Bspng4mE,145561
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.dev27.dist-info/METADATA,sha256=Nr3x_-2IN6jO8C6UVvoDJmObBt8_Y6OZ54Ok0UsHikA,24665
770
- qontract_reconcile-0.10.2.dev27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
- qontract_reconcile-0.10.2.dev27.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
- qontract_reconcile-0.10.2.dev27.dist-info/RECORD,,
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
reconcile/cli.py CHANGED
@@ -1162,28 +1162,15 @@ def jenkins_webhooks_cleaner(ctx):
1162
1162
  "--jira-board-name", help="The Jira board to act on.", default=None, multiple=True
1163
1163
  )
1164
1164
  @click.option("--board-check-interval", help="Check interval in minutes", default=120)
1165
- @enable_extended_early_exit
1166
- @extended_early_exit_cache_ttl_seconds
1167
- @log_cached_log_output
1168
1165
  @click.pass_context
1169
- def jira_permissions_validator(
1170
- ctx,
1171
- jira_board_name,
1172
- board_check_interval,
1173
- enable_extended_early_exit,
1174
- extended_early_exit_cache_ttl_seconds,
1175
- log_cached_log_output,
1176
- ):
1166
+ def jira_permissions_validator(ctx, jira_board_name, board_check_interval):
1177
1167
  import reconcile.jira_permissions_validator
1178
1168
 
1179
1169
  run_integration(
1180
1170
  reconcile.jira_permissions_validator,
1181
1171
  ctx.obj,
1182
1172
  jira_board_name=jira_board_name,
1183
- board_check_interval=board_check_interval,
1184
- enable_extended_early_exit=enable_extended_early_exit,
1185
- extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
1186
- log_cached_log_output=log_cached_log_output,
1173
+ board_check_interval_sec=board_check_interval * 60,
1187
1174
  )
1188
1175
 
1189
1176
 
@@ -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
 
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import random
2
3
  import sys
3
4
  import time
4
5
  from collections.abc import Callable, Iterable
@@ -23,16 +24,11 @@ from reconcile.typed_queries.jiralert_settings import get_jiralert_settings
23
24
  from reconcile.utils import gql, metrics
24
25
  from reconcile.utils.defer import defer
25
26
  from reconcile.utils.disabled_integrations import integration_is_enabled
26
- from reconcile.utils.extended_early_exit import (
27
- ExtendedEarlyExitRunnerResult,
28
- extended_early_exit_run,
29
- )
30
27
  from reconcile.utils.jira_client import JiraClient, JiraWatcherSettings
31
28
  from reconcile.utils.runtime.integration import DesiredStateShardConfig
32
29
  from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
33
30
  from reconcile.utils.semver_helper import make_semver
34
31
  from reconcile.utils.state import State, init_state
35
- from reconcile.utils.unleash import get_feature_toggle_state
36
32
 
37
33
  QONTRACT_INTEGRATION = "jira-permissions-validator"
38
34
  QONTRACT_INTEGRATION_VERSION = make_semver(1, 2, 0)
@@ -70,7 +66,7 @@ class ValidationError(IntFlag):
70
66
 
71
67
  class RunnerParams(TypedDict):
72
68
  boards: list[JiraBoardV1]
73
- board_check_interval: int
69
+ board_check_interval_sec: int
74
70
  dry_run: bool
75
71
 
76
72
 
@@ -206,7 +202,7 @@ def validate_boards(
206
202
  jira_boards: Iterable[JiraBoardV1],
207
203
  default_issue_type: str,
208
204
  default_reopen_state: str,
209
- board_check_interval: int,
205
+ board_check_interval_sec: int,
210
206
  dry_run: bool,
211
207
  state: State,
212
208
  jira_client_class: type[JiraClient] = JiraClient,
@@ -214,8 +210,8 @@ def validate_boards(
214
210
  error = False
215
211
  jira_clients: dict[str, JiraClient] = {}
216
212
  for board in jira_boards:
217
- last_successful_run = state.get(board.name, 0)
218
- if not dry_run and time.time() <= last_successful_run + board_check_interval:
213
+ next_run_time = state.get(board.name, 0)
214
+ if not dry_run and time.time() <= next_run_time:
219
215
  logging.debug(f"[{board.name}] Skipping board")
220
216
  continue
221
217
 
@@ -243,8 +239,13 @@ def validate_boards(
243
239
  # no errors
244
240
  logging.debug(f"[{board.name}] is valid")
245
241
  if not dry_run:
246
- # remember time of the last successful run
247
- state[board.name] = time.time()
242
+ # set the run time for the next check
243
+ state[board.name] = (
244
+ time.time()
245
+ + board_check_interval_sec
246
+ # add some randomness to avoid all boards checking at the same time
247
+ + random.randint(0, 3600)
248
+ )
248
249
  case ValidationError.PERMISSION_ERROR:
249
250
  # we don't have all the permissions, but we can create jira tickets
250
251
  metrics_container.set_gauge(
@@ -290,57 +291,15 @@ def export_boards(boards: list[JiraBoardV1]) -> list[dict]:
290
291
  return [board.dict() for board in boards]
291
292
 
292
293
 
294
+ @defer
293
295
  def run(
294
296
  dry_run: bool,
295
297
  jira_board_name: list[str] | None = None,
296
- board_check_interval: int = 3600,
297
- enable_extended_early_exit: bool = False,
298
- extended_early_exit_cache_ttl_seconds: int = 3600,
299
- log_cached_log_output: bool = False,
298
+ board_check_interval_sec: int = 3600,
299
+ defer: Callable | None = None,
300
300
  ) -> None:
301
301
  gql_api = gql.get_api()
302
302
  boards = get_jira_boards(query_func=gql_api.query, jira_board_names=jira_board_name)
303
- runner_params: RunnerParams = {
304
- "boards": boards,
305
- "dry_run": dry_run,
306
- "board_check_interval": board_check_interval,
307
- }
308
- if enable_extended_early_exit and get_feature_toggle_state(
309
- "jira-permissions-validator-extended-early-exit",
310
- default=True,
311
- ):
312
- vault_settings = get_app_interface_vault_settings()
313
- secret_reader = create_secret_reader(use_vault=vault_settings.vault)
314
-
315
- cache_source = CacheSource(
316
- boards=export_boards(boards),
317
- )
318
- extended_early_exit_run(
319
- integration=QONTRACT_INTEGRATION,
320
- integration_version=QONTRACT_INTEGRATION_VERSION,
321
- # don't use `dry_run` in the cache key because this is a read-only integration
322
- dry_run=False,
323
- cache_source=cache_source,
324
- shard="_".join(set(jira_board_name)) if jira_board_name else "",
325
- ttl_seconds=extended_early_exit_cache_ttl_seconds,
326
- logger=logging.getLogger(),
327
- runner=runner,
328
- runner_params=runner_params,
329
- secret_reader=secret_reader,
330
- log_cached_log_output=log_cached_log_output,
331
- )
332
- else:
333
- runner(**runner_params)
334
-
335
-
336
- @defer
337
- def runner(
338
- boards: list[JiraBoardV1],
339
- dry_run: bool,
340
- board_check_interval: int,
341
- defer: Callable | None = None,
342
- ) -> ExtendedEarlyExitRunnerResult:
343
- gql_api = gql.get_api()
344
303
  settings = get_jira_settings(gql_api=gql_api)
345
304
  jiralert_settings = get_jiralert_settings(query_func=gql_api.query)
346
305
  vault_settings = get_app_interface_vault_settings()
@@ -357,7 +316,7 @@ def runner(
357
316
  jira_boards=boards,
358
317
  default_issue_type=jiralert_settings.default_issue_type,
359
318
  default_reopen_state=jiralert_settings.default_reopen_state,
360
- board_check_interval=board_check_interval,
319
+ board_check_interval_sec=board_check_interval_sec,
361
320
  dry_run=dry_run,
362
321
  state=state,
363
322
  )
@@ -365,8 +324,6 @@ def runner(
365
324
  if error:
366
325
  sys.exit(ExitCodes.ERROR)
367
326
 
368
- return ExtendedEarlyExitRunnerResult(payload=export_boards(boards), applied_count=0)
369
-
370
327
 
371
328
  def early_exit_desired_state(
372
329
  *args: Any, jira_board_name: list[str] | None = None, **kwargs: Any
@@ -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.gitlab_api import GitLabApi
11
- from reconcile.utils.mr.base import MergeRequestBase
12
- from reconcile.utils.mr.labels import AUTO_MERGE
3
+ from reconcile.utils.mr.update_access_report_base import UpdateAccessReportBase
4
+
13
5
 
14
- CURRENT_USERS_TABLE_TEMPLATE = """
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
- glitchtip_access_revalidation_workbook=Path(
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