qontract-reconcile 0.10.1rc860__py3-none-any.whl → 0.10.1rc862__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.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc860
3
+ Version: 0.10.1rc862
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
10
10
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
12
12
  reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
13
- reconcile/cli.py,sha256=F5s-i-OlL6RhqiYkEhXMaNLegiPazbS0ahEtRrO2LeE,104075
13
+ reconcile/cli.py,sha256=1x1ZIR1wKgnGh799yPcV8IQD1RHyHwWlDg9Llh1diOw,104736
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
15
15
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
16
16
  reconcile/dashdotdb_base.py,sha256=R2JuwiXAEYAFiCtnztM_IIr1rtVzPpaWAmgxuDa2FgY,4813
@@ -35,7 +35,7 @@ reconcile/gitlab_labeler.py,sha256=IxE1XM5o4rDOFuR4cM2yAHTy4Uzdg3Nyz2mp7b8Fx1g,4
35
35
  reconcile/gitlab_members.py,sha256=M6LwFOrwgvl1NNdOJa1mrQFUon-bEVv1AyhGeLed454,8443
36
36
  reconcile/gitlab_mr_sqs_consumer.py,sha256=O46mdziPgGOndbU-0_UJKJVUaiEoVzJPEgKm4_UvYoI,2571
37
37
  reconcile/gitlab_owners.py,sha256=sn9njaKOtqcvnhi2qtm-faAfAR4zNqflbSuusA9RUuI,13456
38
- reconcile/gitlab_permissions.py,sha256=K6Ta59c_Hib2DI5DnryiQbDNSOJTiY5B8cGy5a9xNkI,2199
38
+ reconcile/gitlab_permissions.py,sha256=LcO5tVND3Rh-onU6WFh5ZWPCqZ-U4AVuFTZIs_bmPwA,3153
39
39
  reconcile/gitlab_projects.py,sha256=K3tFf_aD1W4Ijp5q-9Qek3kwFGEWPcZ1kd7tzFJ4GyQ,1781
40
40
  reconcile/integrations_manager.py,sha256=J_VV-HINI7YNav2NPIolePZkll-7VBuBXWAyMNhsM_Q,9535
41
41
  reconcile/jenkins_base.py,sha256=0Gocu3fU2YTltaxBlbDQOUvP-7CP2OSQV1ZRwtWeVXw,875
@@ -69,9 +69,10 @@ reconcile/openshift_limitranges.py,sha256=UvCGo_OQ4XoDK55TJmn55qEhhlkhLzhU12tX8n
69
69
  reconcile/openshift_namespace_labels.py,sha256=dLkQgtgsD51WtDHiQOc-lF2yaaFzkiUAZ7ueKUkg-ZM,15669
70
70
  reconcile/openshift_namespaces.py,sha256=nHW1e3dyUWw3JPAzeQeZQ6s2-RuQYaNR7_DUfTP8KOg,5830
71
71
  reconcile/openshift_network_policies.py,sha256=_qqv7yj17OM1J8KJPsFmzFZ85gzESJeBocC672z4_WU,4231
72
+ reconcile/openshift_prometheus_rules.py,sha256=onowXab248zmHH8SbYDTc1W1bl7JiqRFU1xdTkZyLFg,1332
72
73
  reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
73
- reconcile/openshift_resources.py,sha256=WPnSTftrCCHaCDfwSD0CLvs-7GQqay5B7AtM6Swxy7c,1537
74
- reconcile/openshift_resources_base.py,sha256=1ItnO5hq31PnfvrLBxf30ln_keBZcRiLCKcHfmUhYrQ,40826
74
+ reconcile/openshift_resources.py,sha256=I2nO_C37mG3rfyGrd4cGwN3mVseVGuTAHAyhFzLyqF4,1518
75
+ reconcile/openshift_resources_base.py,sha256=_6jm84W3i9JjHs0Wue1p4cJkBYbHwBTvd5UIWPSLCsQ,40924
75
76
  reconcile/openshift_rolebindings.py,sha256=LlImloBisEqzc36jaatic-TeM3hzqMEfxogF-dM4Yhw,6599
76
77
  reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
77
78
  reconcile/openshift_saas_deploy.py,sha256=fmhopPEbyZsGQHRPzyzpKEvoBXEGN3aPxFi7Utq0emU,12788
@@ -500,6 +501,7 @@ reconcile/test/test_github_repo_invites.py,sha256=UVaDlxSxi5iooyUbz8F11d7cvINHLK
500
501
  reconcile/test/test_gitlab_housekeeping.py,sha256=Sn5rERCp28sMiBx5vJaQ5yy80y37GouMClejmXocsT8,10068
501
502
  reconcile/test/test_gitlab_labeler.py,sha256=vFLUJXSIaCduj6wSqgw7Fg7FhlopaDnYI5SLzNHtLoY,4362
502
503
  reconcile/test/test_gitlab_members.py,sha256=kaCOA02eZSrSMkzHmaLwWW3LY6Af0ciLSEP4PlMLvOU,9949
504
+ reconcile/test/test_gitlab_permissions.py,sha256=FAKT7UuNAjxmke90P2cA-924_CGZ7Kp3JLTb6rmTseM,1965
503
505
  reconcile/test/test_instrumented_wrappers.py,sha256=CZzhnQH0c4i7-Rxjg7-0dfFMvVPegLHL46z5NHOOCwo,608
504
506
  reconcile/test/test_integrations_manager.py,sha256=l6KwSFT0NS9VSR-b_9z_ZEGXDWH3EMitUEMC_1h8Xkk,38184
505
507
  reconcile/test/test_jenkins_worker_fleets.py,sha256=o1jlT7OBBSgu0M3iI4xMdz_x6SciF7yhNBpLk5gTJfg,2361
@@ -651,7 +653,7 @@ reconcile/utils/filtering.py,sha256=zZnHH0u0SaTDyzuFXZ_mREURGLvjEqQIQy4z-7QBVlc,
651
653
  reconcile/utils/git.py,sha256=BdxXFgQ1XOZpS-4qb3qMsKTCFDG8MlE26rv1jAhvCkM,1560
652
654
  reconcile/utils/git_secrets.py,sha256=0wGNL5mvDtVPRuu3vEQgld1Am64gIDJHtmu1_ZKxMAI,1973
653
655
  reconcile/utils/github_api.py,sha256=_bttNxYKeam_tLVe27L7O4gKqSn6CeyuFnJn8tSaUVY,2488
654
- reconcile/utils/gitlab_api.py,sha256=9C6oS98w1z-pDAAz5k9kgEvRO6r7hduHoOl7cufFr5s,27400
656
+ reconcile/utils/gitlab_api.py,sha256=kGYNyx6pbF43rd6R3a3wcIGmATiqNUFJUUHP4sELgNI,29027
655
657
  reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
656
658
  reconcile/utils/gql.py,sha256=qMWj39ilJGV1YAeUvbqVYq6u9zxuPvZqxFLaLb6A-bA,14256
657
659
  reconcile/utils/grouping.py,sha256=kWKivD14eAkiDneH_VIl_XyUdcVVQgiaKA9sLsuD2dw,441
@@ -834,8 +836,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
834
836
  tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
835
837
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
836
838
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
837
- qontract_reconcile-0.10.1rc860.dist-info/METADATA,sha256=4f-gD89LH7FTMjujTGPqMsutWxeDQjvCtGKYLLDvlKY,2273
838
- qontract_reconcile-0.10.1rc860.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
839
- qontract_reconcile-0.10.1rc860.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
840
- qontract_reconcile-0.10.1rc860.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
841
- qontract_reconcile-0.10.1rc860.dist-info/RECORD,,
839
+ qontract_reconcile-0.10.1rc862.dist-info/METADATA,sha256=NyCtuFntbeQx8o2HfebLbLpZ_q3fqxZgXGUUgOY7h2Y,2273
840
+ qontract_reconcile-0.10.1rc862.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
841
+ qontract_reconcile-0.10.1rc862.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
842
+ qontract_reconcile-0.10.1rc862.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
843
+ qontract_reconcile-0.10.1rc862.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -1776,6 +1776,31 @@ def openshift_routes(
1776
1776
  )
1777
1777
 
1778
1778
 
1779
+ @integration.command(short_help="Manages OpenShift Prometheus Rules.")
1780
+ @threaded()
1781
+ @binary(["oc", "ssh"])
1782
+ @binary_version("oc", ["version", "--client"], OC_VERSION_REGEX, OC_VERSIONS)
1783
+ @internal()
1784
+ @use_jump_host()
1785
+ @cluster_name
1786
+ @namespace_name
1787
+ @click.pass_context
1788
+ def openshift_prometheus_rules(
1789
+ ctx, thread_pool_size, internal, use_jump_host, cluster_name, namespace_name
1790
+ ):
1791
+ import reconcile.openshift_prometheus_rules
1792
+
1793
+ run_integration(
1794
+ reconcile.openshift_prometheus_rules,
1795
+ ctx.obj,
1796
+ thread_pool_size,
1797
+ internal,
1798
+ use_jump_host,
1799
+ cluster_name=cluster_name,
1800
+ namespace_name=namespace_name,
1801
+ )
1802
+
1803
+
1779
1804
  @integration.command(short_help="Configures the teams and members in Quay.")
1780
1805
  @click.pass_context
1781
1806
  def quay_membership(ctx):
@@ -8,8 +8,10 @@ from reconcile import queries
8
8
  from reconcile.utils import batches
9
9
  from reconcile.utils.defer import defer
10
10
  from reconcile.utils.gitlab_api import GitLabApi
11
+ from reconcile.utils.unleash import get_feature_toggle_state
11
12
 
12
13
  QONTRACT_INTEGRATION = "gitlab-permissions"
14
+ APP_SRE_GROUP_NAME = "app-sre"
13
15
  PAGE_SIZE = 100
14
16
 
15
17
 
@@ -50,6 +52,19 @@ def run(dry_run, thread_pool_size=10, defer=None):
50
52
  if defer:
51
53
  defer(gl.cleanup)
52
54
  repos = queries.get_repos(server=gl.server, exclude_manage_permissions=True)
55
+ share_with_group_enabled = get_feature_toggle_state(
56
+ "gitlab-permissions-share-with-group",
57
+ default=False,
58
+ )
59
+ if share_with_group_enabled:
60
+ share_project_with_group(gl, repos, dry_run)
61
+ else:
62
+ share_project_with_group_members(gl, repos, thread_pool_size, dry_run)
63
+
64
+
65
+ def share_project_with_group_members(
66
+ gl: GitLabApi, repos: list[str], thread_pool_size: int, dry_run: bool
67
+ ) -> None:
53
68
  app_sre = gl.get_app_sre_group_users()
54
69
  results = threaded.run(
55
70
  get_members_to_add, repos, thread_pool_size, gl=gl, app_sre=app_sre
@@ -61,6 +76,14 @@ def run(dry_run, thread_pool_size=10, defer=None):
61
76
  gl.add_project_member(m["repo"], m["user"])
62
77
 
63
78
 
79
+ def share_project_with_group(gl: GitLabApi, repos: list[str], dry_run: bool) -> None:
80
+ group_id, shared_projects = gl.get_group_id_and_shared_projects(APP_SRE_GROUP_NAME)
81
+ shared_project_repos = {project["web_url"] for project in shared_projects}
82
+ repos_to_share = set(repos) - shared_project_repos
83
+ for repo in repos_to_share:
84
+ gl.share_project_with_group(repo_url=repo, group_id=group_id, dry_run=dry_run)
85
+
86
+
64
87
  def early_exit_desired_state(*args, **kwargs) -> dict[str, Any]:
65
88
  instance = queries.get_gitlab_instance()
66
89
  return {
@@ -0,0 +1,43 @@
1
+ from collections.abc import Iterable
2
+ from typing import Any
3
+
4
+ import reconcile.openshift_resources_base as orb
5
+ from reconcile.utils.runtime.integration import DesiredStateShardConfig
6
+ from reconcile.utils.semver_helper import make_semver
7
+
8
+ QONTRACT_INTEGRATION = "openshift-prometheus-rules"
9
+ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
10
+ PROVIDERS = ["prometheus-rule"]
11
+
12
+
13
+ def run(
14
+ dry_run: bool,
15
+ thread_pool_size: int = 10,
16
+ internal: bool | None = None,
17
+ use_jump_host: bool = True,
18
+ cluster_name: Iterable[str] | None = None,
19
+ exclude_cluster: Iterable[str] | None = None,
20
+ namespace_name: str | None = None,
21
+ ) -> None:
22
+ orb.QONTRACT_INTEGRATION = QONTRACT_INTEGRATION
23
+ orb.QONTRACT_INTEGRATION_VERSION = QONTRACT_INTEGRATION_VERSION
24
+
25
+ orb.run(
26
+ dry_run=dry_run,
27
+ thread_pool_size=thread_pool_size,
28
+ internal=internal,
29
+ use_jump_host=use_jump_host,
30
+ providers=PROVIDERS,
31
+ cluster_name=cluster_name,
32
+ exclude_cluster=exclude_cluster,
33
+ namespace_name=namespace_name,
34
+ init_api_resources=True,
35
+ )
36
+
37
+
38
+ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
39
+ return orb.early_exit_desired_state(PROVIDERS)
40
+
41
+
42
+ def desired_state_shard_config() -> DesiredStateShardConfig:
43
+ return orb.desired_state_shard_config()
@@ -8,7 +8,7 @@ from reconcile.utils.semver_helper import make_semver
8
8
 
9
9
  QONTRACT_INTEGRATION = "openshift_resources"
10
10
  QONTRACT_INTEGRATION_VERSION = make_semver(1, 9, 3)
11
- PROVIDERS = ["resource", "resource-template", "prometheus-rule"]
11
+ PROVIDERS = ["resource", "resource-template"]
12
12
 
13
13
 
14
14
  def run(
@@ -835,6 +835,8 @@ def canonicalize_namespaces(
835
835
  override = ["Secret"]
836
836
  elif providers[0] == "route":
837
837
  override = ["Route"]
838
+ elif providers[0] == "prometheus-rule":
839
+ override = ["PrometheusRule"]
838
840
 
839
841
  namespace_info["openshiftResources"] = ors
840
842
  canonicalized_namespaces.append(namespace_info)
@@ -0,0 +1,57 @@
1
+ from unittest.mock import MagicMock, create_autospec
2
+
3
+ import pytest
4
+ from gitlab.v4.objects import CurrentUser, GroupMember
5
+ from pytest_mock import MockerFixture
6
+
7
+ from reconcile import gitlab_permissions
8
+ from reconcile.utils.gitlab_api import GitLabApi
9
+
10
+
11
+ @pytest.fixture()
12
+ def mocked_queries(mocker: MockerFixture) -> MagicMock:
13
+ queries = mocker.patch("reconcile.gitlab_permissions.queries")
14
+ queries.get_gitlab_instance.return_value = {}
15
+ queries.get_app_interface_settings.return_value = {}
16
+ queries.get_repos.return_value = ["https://test-gitlab.com"]
17
+ return queries
18
+
19
+
20
+ @pytest.fixture()
21
+ def mocked_gl() -> MagicMock:
22
+ gl = create_autospec(GitLabApi)
23
+ gl.server = "test_server"
24
+ gl.user = create_autospec(CurrentUser)
25
+ gl.user.username = "test_name"
26
+ return gl
27
+
28
+
29
+ def test_run_share_with_members(
30
+ mocked_queries: MagicMock, mocker: MockerFixture, mocked_gl: MagicMock
31
+ ) -> None:
32
+ mocker.patch("reconcile.gitlab_permissions.GitLabApi").return_value = mocked_gl
33
+ mocked_gl.get_app_sre_group_users.return_value = [
34
+ create_autospec(GroupMember, id=123, username="test_name2")
35
+ ]
36
+ mocker.patch(
37
+ "reconcile.gitlab_permissions.get_feature_toggle_state"
38
+ ).return_value = False
39
+ mocked_gl.get_project_maintainers.return_value = ["test_name"]
40
+
41
+ gitlab_permissions.run(False, thread_pool_size=1)
42
+ mocked_gl.add_project_member.assert_called_once()
43
+
44
+
45
+ def test_run_share_with_group(
46
+ mocked_queries: MagicMock, mocker: MockerFixture, mocked_gl: MagicMock
47
+ ) -> None:
48
+ mocker.patch("reconcile.gitlab_permissions.GitLabApi").return_value = mocked_gl
49
+ mocker.patch(
50
+ "reconcile.gitlab_permissions.get_feature_toggle_state"
51
+ ).return_value = True
52
+ mocked_gl.get_group_id_and_shared_projects.return_value = (
53
+ 1234,
54
+ [{"web_url": "https://test.com"}],
55
+ )
56
+ gitlab_permissions.run(False, thread_pool_size=1)
57
+ mocked_gl.share_project_with_group.assert_called_once()
@@ -19,6 +19,13 @@ from urllib.parse import urlparse
19
19
 
20
20
  import gitlab
21
21
  import urllib3
22
+ from gitlab.const import (
23
+ DEVELOPER_ACCESS,
24
+ GUEST_ACCESS,
25
+ MAINTAINER_ACCESS,
26
+ OWNER_ACCESS,
27
+ REPORTER_ACCESS,
28
+ )
22
29
  from gitlab.v4.objects import (
23
30
  CurrentUser,
24
31
  Group,
@@ -250,6 +257,46 @@ class GitLabApi: # pylint: disable=too-many-public-methods
250
257
  except gitlab.exceptions.GitlabGetError:
251
258
  return None
252
259
 
260
+ def share_project_with_group(
261
+ self, repo_url: str, group_id: int, dry_run: bool, access: str = "maintainer"
262
+ ) -> None:
263
+ project = self.get_project(repo_url)
264
+ if project is None:
265
+ return None
266
+ access_level = self.get_access_level(access)
267
+ # check if we have 'access_level' access so we can add the group with same role.
268
+ members = self.get_items(
269
+ project.members.all, query_parameters={"user_ids": self.user.id}
270
+ )
271
+ if not any(
272
+ self.user.id == member.id and member.access_level >= access_level
273
+ for member in members
274
+ ):
275
+ logging.error(
276
+ "%s is not shared with %s as %s",
277
+ repo_url,
278
+ self.user.username,
279
+ access,
280
+ )
281
+ return None
282
+ logging.info(["add_group_as_maintainer", repo_url, group_id])
283
+ if not dry_run:
284
+ gitlab_request.labels(integration=INTEGRATION_NAME).inc()
285
+ project.share(group_id, access_level)
286
+
287
+ def get_group_id_and_shared_projects(
288
+ self, group_name: str
289
+ ) -> tuple[int, list[dict]]:
290
+ gitlab_request.labels(integration=INTEGRATION_NAME).inc()
291
+ group = self.gl.groups.get(group_name)
292
+ return group.id, [
293
+ project
294
+ for project in group.shared_projects
295
+ for shared_group in project["shared_with_groups"]
296
+ if shared_group["group_id"] == group.id
297
+ and shared_group["group_access_level"] >= MAINTAINER_ACCESS
298
+ ]
299
+
253
300
  @staticmethod
254
301
  def _is_bot_username(username: str) -> bool:
255
302
  """crudely checking for the username
@@ -331,30 +378,30 @@ class GitLabApi: # pylint: disable=too-many-public-methods
331
378
 
332
379
  @staticmethod
333
380
  def get_access_level_string(access_level):
334
- if access_level == gitlab.OWNER_ACCESS:
381
+ if access_level == OWNER_ACCESS:
335
382
  return "owner"
336
- if access_level == gitlab.MAINTAINER_ACCESS:
383
+ if access_level == MAINTAINER_ACCESS:
337
384
  return "maintainer"
338
- if access_level == gitlab.DEVELOPER_ACCESS:
385
+ if access_level == DEVELOPER_ACCESS:
339
386
  return "developer"
340
- if access_level == gitlab.REPORTER_ACCESS:
387
+ if access_level == REPORTER_ACCESS:
341
388
  return "reporter"
342
- if access_level == gitlab.GUEST_ACCESS:
389
+ if access_level == GUEST_ACCESS:
343
390
  return "guest"
344
391
 
345
392
  @staticmethod
346
393
  def get_access_level(access):
347
394
  access = access.lower()
348
395
  if access == "owner":
349
- return gitlab.OWNER_ACCESS
396
+ return OWNER_ACCESS
350
397
  if access == "maintainer":
351
- return gitlab.MAINTAINER_ACCESS
398
+ return MAINTAINER_ACCESS
352
399
  if access == "developer":
353
- return gitlab.DEVELOPER_ACCESS
400
+ return DEVELOPER_ACCESS
354
401
  if access == "reporter":
355
- return gitlab.REPORTER_ACCESS
402
+ return REPORTER_ACCESS
356
403
  if access == "guest":
357
- return gitlab.GUEST_ACCESS
404
+ return GUEST_ACCESS
358
405
 
359
406
  def get_group_id_and_projects(self, group_name: str) -> tuple[str, list[str]]:
360
407
  gitlab_request.labels(integration=INTEGRATION_NAME).inc()