qontract-reconcile 0.10.1rc772__py3-none-any.whl → 0.10.1rc774__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.1rc772
3
+ Version: 0.10.1rc774
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
@@ -15,7 +15,7 @@ reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w
15
15
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
16
16
  reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
17
17
  reconcile/dashdotdb_cso.py,sha256=FoXrWGpOwXG5jf0eklN84tjJVUAYzKat7rtq_28JMlQ,3672
18
- reconcile/dashdotdb_dora.py,sha256=n9EJXhxCoMYuldj4Fa5s0TqfiiolSrqDEOCaLBV3uag,17235
18
+ reconcile/dashdotdb_dora.py,sha256=hwGqjRbySKJeP7ekN96fwW5UFUpTWiQ-IZ_tzWQYbKY,17565
19
19
  reconcile/dashdotdb_dvo.py,sha256=YXqpI6fBQAql-ybGI0grj9gWMzmKiAvPE__pNju6obk,8996
20
20
  reconcile/dashdotdb_slo.py,sha256=bf1WSh5JP9obHVQsMy0OO71_VTYZgwAopElFZM6DmRo,6714
21
21
  reconcile/database_access_manager.py,sha256=42dBJyihdwx4WjEBjwi3lUiDzQ1t_2ZFViJri2c4_aE,25716
@@ -104,7 +104,7 @@ reconcile/sendgrid_teammates.py,sha256=oO8QbLb4s1o8A6CGiCagN9CmS05BSS_WLztuY0Ym9
104
104
  reconcile/service_dependencies.py,sha256=PMKP9vc6oL-78rzyF_RE8DzLSQMSqN8vCqt9sWpBLAM,4470
105
105
  reconcile/signalfx_endpoint_monitoring.py,sha256=D1m8iq0EAKie0OD59FOcVCtpWWZ7xlo6lwBS9urwMIk,2894
106
106
  reconcile/slack_base.py,sha256=K3fSYx46G1djoPb07_C9j6ChhMCt5LgV5l6v2TFkNZk,3479
107
- reconcile/slack_usergroups.py,sha256=XC7bYHP322EFWrEaR4Orw_sOoMFmuV3o08OC0UE4xLQ,27592
107
+ reconcile/slack_usergroups.py,sha256=6G-KjcqacqqhJDsl-KMC-t5M9oexUpSSUc1yGyt2zvY,27120
108
108
  reconcile/sql_query.py,sha256=FAQI9EIHsokZBbGwvGU4vnjg1fHemxpYQE20UtCB1qo,25941
109
109
  reconcile/status.py,sha256=cY4IJFXemhxptRJqR4qaaOWqei9e4jgLXuVSGajMsjg,544
110
110
  reconcile/status_board.py,sha256=nA74_133jukxVShjPKJpkXOA3vggDTTEhYTegoXbN1M,8632
@@ -514,7 +514,7 @@ reconcile/test/test_saasherder.py,sha256=1_GyiXxxNqKSKE7PrtFJL7tUFg77d1oQPZzNBZW
514
514
  reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
515
515
  reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
516
516
  reconcile/test/test_slack_base.py,sha256=gpbWOLNxMMX6fyAbs1JakhLTnwfedb3f7WpUae4tQZE,5060
517
- reconcile/test/test_slack_usergroups.py,sha256=UO-OzqbGMSpNsyddu2TbWOzSiVze6Bq95Ph1QIJqLWY,23990
517
+ reconcile/test/test_slack_usergroups.py,sha256=wmS7xgl4U1jx4gu8qIDlljkhZxb_y8F6tR4UteyrlZE,24899
518
518
  reconcile/test/test_sql_query.py,sha256=rC-lf1_isT9i2ZIV9W0hkUkLi2oBIjZMRMhk-6mV-34,11029
519
519
  reconcile/test/test_status_board.py,sha256=go3YSWo03OLIdK95SuiDJa1Nqk-eN_9QtS7dfmu9__8,7875
520
520
  reconcile/test/test_terraform_aws_route53.py,sha256=xHggb8K1P76OyCfFcogbkmyKle-NlUylcbDnuv3IqvY,771
@@ -632,7 +632,7 @@ reconcile/utils/keycloak.py,sha256=UqOsAcHKmmIunroWB5YzC1fUZ3S3aq6L7trn6vLRmXY,3
632
632
  reconcile/utils/ldap_client.py,sha256=ho4veSrHqQWs0YhLFyKeD-duCwY8Nc5gUIA5qLENuMY,2502
633
633
  reconcile/utils/lean_terraform_client.py,sha256=zReyNPJbr2uOdrdh8Qfe-OZQBoRwxb5Za_ddeoUCYVk,4064
634
634
  reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
635
- reconcile/utils/metrics.py,sha256=7nXdctmZ0UtGMHPpS3V55sfH4xpMPqdYaJ3JKAUc_sM,18474
635
+ reconcile/utils/metrics.py,sha256=ot4dBO-KLZRowvNozm7jG0RWjcVsH1SL-lQ0jJgBBZM,18645
636
636
  reconcile/utils/models.py,sha256=It_Q1WNIvw_EDCsiSWzIgpSPr_X9jMgbJI-DR3N23xY,4677
637
637
  reconcile/utils/oauth2_backend_application_session.py,sha256=6W16sMpnWEPFDUX7qi5Cui2yOnmLfpgUxWtB3Ii35D0,4177
638
638
  reconcile/utils/oc.py,sha256=ILAlP-AZMtWeyAepLoMnYbDJfyyMs-Z0fOEo9JXQfkE,65490
@@ -657,7 +657,7 @@ reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
657
657
  reconcile/utils/secret_reader.py,sha256=2DeYAAQFjUULEKlLw3UDAUoND6gbqvCh9uKPtlc-0us,10403
658
658
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
659
659
  reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,839
660
- reconcile/utils/slack_api.py,sha256=C-VThgYRtrRWraq9ZE6hEf1bXrwzRDCDK0uRw3DP6ew,16425
660
+ reconcile/utils/slack_api.py,sha256=2t9jeCS7V3sHSMk-ByRcjmh-2uVvGELCfJqm1nu_hKI,17395
661
661
  reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
662
662
  reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4dA,2287
663
663
  reconcile/utils/state.py,sha256=FK8NLT1xyumuXpYRm0Nk6pWpOE_U6-NovGn6zKCw8vw,16298
@@ -785,8 +785,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
785
785
  tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
786
786
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
787
787
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
788
- qontract_reconcile-0.10.1rc772.dist-info/METADATA,sha256=ggkGwJEVc2hcdi_OZpg-AGRT0hX8-c-5bD6NTbwG6ro,2382
789
- qontract_reconcile-0.10.1rc772.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
790
- qontract_reconcile-0.10.1rc772.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
791
- qontract_reconcile-0.10.1rc772.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
792
- qontract_reconcile-0.10.1rc772.dist-info/RECORD,,
788
+ qontract_reconcile-0.10.1rc774.dist-info/METADATA,sha256=I9lXAkituklwaEp2VxZU_rBUp1jOMlcGDN7XisBfH28,2382
789
+ qontract_reconcile-0.10.1rc774.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
790
+ qontract_reconcile-0.10.1rc774.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
791
+ qontract_reconcile-0.10.1rc774.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
792
+ qontract_reconcile-0.10.1rc774.dist-info/RECORD,,
@@ -17,6 +17,7 @@ from typing import (
17
17
 
18
18
  import requests
19
19
  import yaml
20
+ from github import GithubException
20
21
  from psycopg2 import (
21
22
  connect,
22
23
  sql,
@@ -418,7 +419,14 @@ class DashdotdbDORA(DashdotdbBase):
418
419
 
419
420
  LOG.info("Fetching commits %s", rc)
420
421
  if rc.repo_url.startswith("https://github.com"):
421
- commits = self._github_compare_commits(rc)
422
+ try:
423
+ commits = self._github_compare_commits(rc)
424
+ except GithubException as e:
425
+ if e.status == 404:
426
+ LOG.info(
427
+ f"Ignoring RepoChanges for {rc} because could not calculate them: {e.data['message']}"
428
+ )
429
+ return rc, []
422
430
  elif rc.repo_url.startswith(self.gl.server):
423
431
  commits = self._gitlab_compare_commits(rc)
424
432
  else:
@@ -110,8 +110,9 @@ class State(BaseModel):
110
110
  usergroup: str = ""
111
111
  description: str = ""
112
112
  users: set[SlackObject] = set()
113
+ user_names: set[str] = set()
113
114
  channels: set[SlackObject] = set()
114
- usergroup_id: Optional[str] = None
115
+ channel_names: set[str] = set()
115
116
 
116
117
  def __bool__(self) -> bool:
117
118
  return self.workspace != "" # noqa: PLC1901
@@ -392,7 +393,6 @@ def include_user_to_cluster_usergroup(
392
393
 
393
394
 
394
395
  def get_desired_state(
395
- slack_map: SlackMap,
396
396
  pagerduty_map: PagerDutyMap,
397
397
  permissions: Iterable[PermissionSlackUsergroupV1],
398
398
  users: Iterable[User],
@@ -418,9 +418,6 @@ def get_desired_state(
418
418
  not in managed usergroups {p.workspace.managed_usergroups}"
419
419
  )
420
420
 
421
- slack = slack_map[p.workspace.name].slack
422
- ugid = slack.get_usergroup_id(usergroup)
423
-
424
421
  all_user_names = [get_slack_username(u) for r in p.roles or [] for u in r.users]
425
422
  slack_usernames_pagerduty = get_usernames_from_pagerduty(
426
423
  pagerduties=p.pagerduty or [],
@@ -442,27 +439,16 @@ def get_desired_state(
442
439
  )
443
440
  all_user_names.extend(slack_usernames_schedule)
444
441
 
445
- user_names = list(set(all_user_names))
446
- slack_users = {
447
- SlackObject(pk=pk, name=name)
448
- for pk, name in slack.get_users_by_names(sorted(user_names)).items()
449
- }
450
- slack_channels = {
451
- SlackObject(pk=pk, name=name)
452
- for pk, name in slack.get_channels_by_names(
453
- sorted(p.channels or [])
454
- ).items()
455
- }
442
+ user_names = set(all_user_names)
456
443
 
457
444
  try:
458
- desired_state[p.workspace.name][usergroup].users.update(slack_users)
445
+ desired_state[p.workspace.name][usergroup].user_names.update(user_names)
459
446
  except KeyError:
460
447
  desired_state.setdefault(p.workspace.name, {})[usergroup] = State(
461
448
  workspace=p.workspace.name,
462
449
  usergroup=usergroup,
463
- usergroup_id=ugid,
464
- users=slack_users,
465
- channels=slack_channels,
450
+ user_names=user_names,
451
+ channel_names=sorted(p.channels or []),
466
452
  description=p.description,
467
453
  )
468
454
  return desired_state
@@ -492,7 +478,7 @@ def get_desired_state_cluster_usergroups(
492
478
  for u in openshift_users_desired_state
493
479
  if u["cluster"] == cluster.name
494
480
  ]
495
- cluster_usernames = list({
481
+ cluster_usernames = set({
496
482
  get_slack_username(u)
497
483
  for u in users
498
484
  if include_user_to_cluster_usergroup(u, cluster, desired_cluster_users)
@@ -509,29 +495,16 @@ def get_desired_state_cluster_usergroups(
509
495
  if desired_workspace_name and desired_workspace_name != workspace:
510
496
  continue
511
497
 
512
- ugid = spec.slack.get_usergroup_id(cluster_user_group)
513
- slack_users = {
514
- SlackObject(pk=pk, name=name)
515
- for pk, name in spec.slack.get_users_by_names(
516
- sorted(cluster_usernames)
517
- ).items()
518
- }
519
- slack_channels = {
520
- SlackObject(pk=pk, name=name)
521
- for pk, name in spec.slack.get_channels_by_names([
522
- spec.slack.channel
523
- ]).items()
524
- }
525
-
526
498
  try:
527
- desired_state[workspace][cluster_user_group].users.update(slack_users)
499
+ desired_state[workspace][cluster_user_group].user_names.update(
500
+ cluster_usernames
501
+ )
528
502
  except KeyError:
529
503
  desired_state.setdefault(workspace, {})[cluster_user_group] = State(
530
504
  workspace=workspace,
531
505
  usergroup=cluster_user_group,
532
- usergroup_id=ugid,
533
- users=slack_users,
534
- channels=slack_channels,
506
+ user_names=cluster_usernames,
507
+ channel_names=set([spec.slack.channel]),
535
508
  description=f"Users with access to the {cluster.name} cluster",
536
509
  )
537
510
  return desired_state
@@ -558,8 +531,7 @@ def _create_usergroups(
558
531
  ])
559
532
  if not dry_run:
560
533
  try:
561
- usergroup_id = slack_client.create_usergroup(desired_ug_state.usergroup)
562
- desired_ug_state.usergroup_id = usergroup_id
534
+ slack_client.create_usergroup(desired_ug_state.usergroup)
563
535
  except SlackApiError as error:
564
536
  logging.error(error)
565
537
  error_occurred = True
@@ -597,13 +569,14 @@ def _update_usergroup_users_from_state(
597
569
 
598
570
  if not dry_run:
599
571
  try:
600
- if not desired_ug_state.usergroup_id:
572
+ ugid = slack_client.get_usergroup_id(desired_ug_state.usergroup)
573
+ if not ugid:
601
574
  logging.info(
602
575
  f"Usergroup {desired_ug_state.usergroup} does not exist yet. Skipping for now."
603
576
  )
604
577
  return
605
578
  slack_client.update_usergroup_users(
606
- id=desired_ug_state.usergroup_id,
579
+ id=ugid,
607
580
  users_list=sorted([user.pk for user in desired_ug_state.users]),
608
581
  )
609
582
  except SlackApiError as error:
@@ -658,13 +631,14 @@ def _update_usergroup_from_state(
658
631
 
659
632
  if not dry_run:
660
633
  try:
661
- if not desired_ug_state.usergroup_id:
634
+ ugid = slack_client.get_usergroup_id(desired_ug_state.usergroup)
635
+ if not ugid:
662
636
  logging.info(
663
637
  f"Usergroup {desired_ug_state.usergroup} does not exist yet. Skipping for now."
664
638
  )
665
639
  return
666
640
  slack_client.update_usergroup(
667
- id=desired_ug_state.usergroup_id,
641
+ id=ugid,
668
642
  channels_list=sorted([
669
643
  channel.pk for channel in desired_ug_state.channels
670
644
  ]),
@@ -689,24 +663,40 @@ def act(
689
663
  usergroup, State()
690
664
  )
691
665
 
666
+ slack_client = slack_map[workspace].slack
667
+
668
+ desired_ug_state.users = {
669
+ SlackObject(pk=pk, name=name)
670
+ for pk, name in slack_client.get_users_by_names(
671
+ sorted(desired_ug_state.user_names)
672
+ ).items()
673
+ }
674
+
675
+ desired_ug_state.channels = {
676
+ SlackObject(pk=pk, name=name)
677
+ for pk, name in slack_client.get_channels_by_names(
678
+ sorted(desired_ug_state.channel_names or [])
679
+ ).items()
680
+ }
681
+
692
682
  _create_usergroups(
693
683
  current_ug_state,
694
684
  desired_ug_state,
695
- slack_client=slack_map[workspace].slack,
685
+ slack_client=slack_client,
696
686
  dry_run=dry_run,
697
687
  )
698
688
 
699
689
  _update_usergroup_users_from_state(
700
690
  current_ug_state,
701
691
  desired_ug_state,
702
- slack_client=slack_map[workspace].slack,
692
+ slack_client=slack_client,
703
693
  dry_run=dry_run,
704
694
  )
705
695
 
706
696
  _update_usergroup_from_state(
707
697
  current_ug_state,
708
698
  desired_ug_state,
709
- slack_client=slack_map[workspace].slack,
699
+ slack_client=slack_client,
710
700
  dry_run=dry_run,
711
701
  )
712
702
 
@@ -760,7 +750,6 @@ def run(
760
750
 
761
751
  # run
762
752
  desired_state = get_desired_state(
763
- slack_map=slack_map,
764
753
  pagerduty_map=pagerduty_map,
765
754
  permissions=permissions,
766
755
  users=users,
@@ -776,6 +765,7 @@ def run(
776
765
  )
777
766
  # merge the two desired states recursively
778
767
  desired_state = deep_update(desired_state, desired_state_cluster_usergroups)
768
+
779
769
  current_state = get_current_state(
780
770
  slack_map=slack_map,
781
771
  desired_workspace_name=workspace_name,
@@ -62,7 +62,9 @@ def base_state():
62
62
  usergroup="usergroup-1",
63
63
  usergroup_id="USERGA",
64
64
  users={SlackObject(name="username", pk="USERA")},
65
+ user_names={"username"},
65
66
  channels={SlackObject(name="channelname", pk="CHANA")},
67
+ channel_names={"channelname"},
66
68
  description="Some description",
67
69
  )
68
70
  }
@@ -402,8 +404,6 @@ def test_include_user_to_cluster_usergroup(mocker: MockerFixture, user: UserV1)
402
404
  def test_get_desired_state(
403
405
  mocker: MockerFixture,
404
406
  permissions: Sequence[PermissionSlackUsergroupV1],
405
- slack_map: SlackMap,
406
- slack_client_mock: Mock,
407
407
  user: UserV1,
408
408
  ) -> None:
409
409
  mocker.patch(
@@ -413,21 +413,13 @@ def test_get_desired_state(
413
413
  "reconcile.slack_usergroups.get_slack_usernames_from_owners"
414
414
  ).return_value = ["repo-user"]
415
415
  mock_pagerduty_map = create_autospec(PagerDutyMap)
416
- slack_client_mock.get_usergroup_id.return_value = "ugid"
417
416
  result = integ.get_desired_state(
418
- slack_map,
419
417
  mock_pagerduty_map,
420
418
  permissions[1:],
421
419
  [user],
422
420
  desired_workspace_name=None,
423
421
  desired_usergroup_name=None,
424
422
  )
425
- assert slack_client_mock.get_users_by_names.call_args_list == [
426
- call(["repo-user", "slack_username", "user1"]),
427
- ]
428
- assert slack_client_mock.get_channels_by_names.call_args_list == [
429
- call(["sd-sre-platform", "sre-operators"])
430
- ]
431
423
 
432
424
  assert result == {
433
425
  "coreos": {
@@ -436,7 +428,9 @@ def test_get_desired_state(
436
428
  usergroup="saas-osd-operators",
437
429
  description="SREP managed-cluster-config owners (managed via app-interface)",
438
430
  users=set(),
431
+ user_names={"repo-user", "user1", "slack_username"},
439
432
  channels=set(),
433
+ channel_names={"sre-operators", "sd-sre-platform"},
440
434
  usergroup_id="ugid",
441
435
  )
442
436
  }
@@ -444,26 +438,18 @@ def test_get_desired_state(
444
438
 
445
439
 
446
440
  def test_get_desired_state_cluster_usergroups(
447
- mocker: MockerFixture, slack_map: SlackMap, slack_client_mock: Mock, user: UserV1
441
+ mocker: MockerFixture, slack_map: SlackMap, user: UserV1
448
442
  ) -> None:
449
443
  mocker.patch("reconcile.openshift_users.fetch_desired_state", autospec=True)
450
444
  mocker.patch(
451
445
  "reconcile.slack_usergroups.include_user_to_cluster_usergroup"
452
446
  ).return_value = True
453
- slack_client_mock.get_usergroup_id.return_value = "ugid"
454
447
 
455
448
  cluster = ClusterV1(name="cluster1", auth=[], disable={"integrations": []})
456
449
  result = integ.get_desired_state_cluster_usergroups(
457
450
  slack_map, [cluster], [user], None, None
458
451
  )
459
- assert slack_client_mock.get_users_by_names.call_args_list == [
460
- call(["slack"]),
461
- call(["slack"]),
462
- ]
463
- assert slack_client_mock.get_channels_by_names.call_args_list == [
464
- call(["channel"]),
465
- call(["channel"]),
466
- ]
452
+
467
453
  assert result == {
468
454
  "coreos": {
469
455
  "cluster1-cluster": State(
@@ -471,7 +457,9 @@ def test_get_desired_state_cluster_usergroups(
471
457
  usergroup="cluster1-cluster",
472
458
  description="Users with access to the cluster1 cluster",
473
459
  users=set(),
460
+ user_names={"slack"},
474
461
  channels=set(),
462
+ channel_names={"channel"},
475
463
  usergroup_id="ugid",
476
464
  )
477
465
  },
@@ -481,7 +469,9 @@ def test_get_desired_state_cluster_usergroups(
481
469
  usergroup="cluster1-cluster",
482
470
  description="Users with access to the cluster1 cluster",
483
471
  users=set(),
472
+ user_names={"slack"},
484
473
  channels=set(),
474
+ channel_names={"channel"},
485
475
  usergroup_id="ugid",
486
476
  )
487
477
  },
@@ -489,26 +479,18 @@ def test_get_desired_state_cluster_usergroups(
489
479
 
490
480
 
491
481
  def test_get_desired_state_non_existing_usergroup(
492
- mocker: MockerFixture, slack_map: SlackMap, slack_client_mock: Mock, user: UserV1
482
+ mocker: MockerFixture, slack_map: SlackMap, user: UserV1
493
483
  ) -> None:
494
484
  mocker.patch("reconcile.openshift_users.fetch_desired_state", autospec=True)
495
485
  mocker.patch(
496
486
  "reconcile.slack_usergroups.include_user_to_cluster_usergroup"
497
487
  ).return_value = True
498
- slack_client_mock.get_usergroup_id.return_value = None
499
488
 
500
489
  cluster = ClusterV1(name="cluster1", auth=[], disable={"integrations": []})
501
490
  result = integ.get_desired_state_cluster_usergroups(
502
491
  slack_map, [cluster], [user], None, None
503
492
  )
504
- assert slack_client_mock.get_users_by_names.call_args_list == [
505
- call(["slack"]),
506
- call(["slack"]),
507
- ]
508
- assert slack_client_mock.get_channels_by_names.call_args_list == [
509
- call(["channel"]),
510
- call(["channel"]),
511
- ]
493
+
512
494
  assert result == {
513
495
  "coreos": {
514
496
  "cluster1-cluster": State(
@@ -516,7 +498,9 @@ def test_get_desired_state_non_existing_usergroup(
516
498
  usergroup="cluster1-cluster",
517
499
  description="Users with access to the cluster1 cluster",
518
500
  users=set(),
501
+ user_names={"slack"},
519
502
  channels=set(),
503
+ channel_names={"channel"},
520
504
  usergroup_id=None,
521
505
  )
522
506
  },
@@ -526,7 +510,9 @@ def test_get_desired_state_non_existing_usergroup(
526
510
  usergroup="cluster1-cluster",
527
511
  description="Users with access to the cluster1 cluster",
528
512
  users=set(),
513
+ user_names={"slack"},
529
514
  channels=set(),
515
+ channel_names={"channel"},
530
516
  usergroup_id=None,
531
517
  )
532
518
  },
@@ -597,6 +583,9 @@ def test_act_empty_current_state(
597
583
  desired_state = base_state
598
584
 
599
585
  slack_client_mock.create_usergroup.return_value = "USERGA"
586
+ slack_client_mock.get_usergroup_id.return_value = "USERGA"
587
+ slack_client_mock.get_users_by_names.return_value = {"USERA": "username"}
588
+ slack_client_mock.get_channels_by_names.return_value = {"CHANA": "someotherchannel"}
600
589
 
601
590
  act(current_state, desired_state, slack_map, dry_run=False)
602
591
 
@@ -615,10 +604,12 @@ def test_act_update_usergroup_users(
615
604
  current_state = base_state
616
605
  desired_state = copy.deepcopy(base_state)
617
606
 
618
- desired_state["slack-workspace"]["usergroup-1"].users = {
619
- SlackObject(name="someotherusername", pk="USERB"),
620
- SlackObject(name="anotheruser", pk="USERC"),
607
+ slack_client_mock.get_usergroup_id.return_value = "USERGA"
608
+ slack_client_mock.get_users_by_names.return_value = {
609
+ "USERB": "someotherusername",
610
+ "USERC": "anotheruser",
621
611
  }
612
+ slack_client_mock.get_channels_by_names.return_value = {"CHANA": "channelname"}
622
613
 
623
614
  act(current_state, desired_state, slack_map, dry_run=False)
624
615
 
@@ -634,9 +625,11 @@ def test_act_update_usergroup_channels(
634
625
  current_state = base_state
635
626
  desired_state = copy.deepcopy(base_state)
636
627
 
637
- desired_state["slack-workspace"]["usergroup-1"].channels = {
638
- SlackObject(pk="CHANB", name="someotherchannel")
639
- }
628
+ desired_state["slack-workspace"]["usergroup-1"].channel_names = {"CHANB"}
629
+
630
+ slack_client_mock.get_usergroup_id.return_value = "USERGA"
631
+ slack_client_mock.get_users_by_names.return_value = {"USERA": "username"}
632
+ slack_client_mock.get_channels_by_names.return_value = {"CHANB": "channel"}
640
633
 
641
634
  act(current_state, desired_state, slack_map, dry_run=False)
642
635
 
@@ -656,6 +649,10 @@ def test_act_update_usergroup_description(
656
649
  "usergroup-1"
657
650
  ].description = "A different description"
658
651
 
652
+ slack_client_mock.get_usergroup_id.return_value = "USERGA"
653
+ slack_client_mock.get_users_by_names.return_value = {"USERA": "username"}
654
+ slack_client_mock.get_channels_by_names.return_value = {"CHANA": "channel"}
655
+
659
656
  act(current_state, desired_state, slack_map, dry_run=False)
660
657
 
661
658
  assert slack_client_mock.update_usergroup.call_args_list == [
@@ -675,9 +672,10 @@ def test_act_update_usergroup_desc_and_channels(
675
672
  desired_state["slack-workspace"][
676
673
  "usergroup-1"
677
674
  ].description = "A different description"
678
- desired_state["slack-workspace"]["usergroup-1"].channels = {
679
- SlackObject(pk="CHANB", name="someotherchannel")
680
- }
675
+
676
+ slack_client_mock.get_usergroup_id.return_value = "USERGA"
677
+ slack_client_mock.get_users_by_names.return_value = {"USERA": "username"}
678
+ slack_client_mock.get_channels_by_names.return_value = {"CHANB": "someotherchannel"}
681
679
 
682
680
  act(current_state, desired_state, slack_map, dry_run=False)
683
681
 
@@ -692,21 +690,40 @@ def test_act_update_usergroup_desc_and_channels(
692
690
  def test_act_add_new_usergroups(
693
691
  base_state: SlackState, slack_map: SlackMap, slack_client_mock: Mock
694
692
  ) -> None:
693
+ def get_users(users: set[str]) -> dict[str, str]:
694
+ if "username" in users:
695
+ return {"USERA": "username"}
696
+ if "userb" in users:
697
+ return {"USERB": "userb", "USERC": "userc"}
698
+ return {"USERF": "userf", "USERG": "userg"}
699
+
700
+ def get_channels(channels: set[str]) -> dict[str, str]:
701
+ if "channelname" in channels:
702
+ return {"CHANA": "channelname"}
703
+ if "channelb" in channels:
704
+ return {"CHANB": "channelb", "CHANC": "channelc"}
705
+ return {"CHANF": "channelf", "CHANG": "channelg"}
706
+
707
+ def get_ugid(usergroup: str) -> str:
708
+ if usergroup == "usergroup-1":
709
+ return "USERGA"
710
+ if usergroup == "usergroup-2":
711
+ return "USERGB"
712
+ return "USERGC"
713
+
695
714
  current_state = base_state
696
715
  desired_state = copy.deepcopy(base_state)
697
716
 
717
+ slack_client_mock.get_usergroup_id.side_effect = get_ugid
718
+ slack_client_mock.get_users_by_names.side_effect = get_users
719
+ slack_client_mock.get_channels_by_names.side_effect = get_channels
720
+
698
721
  desired_state["slack-workspace"]["usergroup-2"] = State(
699
722
  workspace="slack-workspace",
700
723
  usergroup="usergroup-2",
701
724
  usergroup_id="USERGB",
702
- users=[
703
- SlackObject(pk="USERB", name="userb"),
704
- SlackObject(pk="USERC", name="userc"),
705
- ],
706
- channels=[
707
- SlackObject(pk="CHANB", name="channelb"),
708
- SlackObject(pk="CHANC", name="channelc"),
709
- ],
725
+ user_names={"userb", "userc"},
726
+ channel_names={"channelb", "channelc"},
710
727
  description="A new usergroup",
711
728
  )
712
729
 
@@ -714,14 +731,8 @@ def test_act_add_new_usergroups(
714
731
  workspace="slack-workspace",
715
732
  usergroup="usergroup-3",
716
733
  usergroup_id="USERGC",
717
- users=[
718
- SlackObject(pk="USERF", name="userf"),
719
- SlackObject(pk="USERG", name="userg"),
720
- ],
721
- channels=[
722
- SlackObject(pk="CHANF", name="channelf"),
723
- SlackObject(pk="CHANG", name="channelg"),
724
- ],
734
+ user_names={"userf", "userg"},
735
+ channel_names={"channelf", "channelg"},
725
736
  description="Another new usergroup",
726
737
  )
727
738
  slack_client_mock.create_usergroup.side_effect = ["USERGB", "USERGC"]
@@ -115,6 +115,12 @@ ocm_request = Counter(
115
115
  labelnames=["verb", "client_id"],
116
116
  )
117
117
 
118
+ slack_request = Counter(
119
+ name="qontract_reconcile_slack_request_total",
120
+ documentation="Number of calls made to Slack API",
121
+ labelnames=["resource", "verb"],
122
+ )
123
+
118
124
 
119
125
  #
120
126
  # Class based metrics
@@ -24,6 +24,8 @@ from slack_sdk.http_retry import (
24
24
  RetryState,
25
25
  )
26
26
 
27
+ from reconcile.utils.metrics import slack_request
28
+
27
29
  MAX_RETRIES = 5
28
30
  TIMEOUT = 30
29
31
 
@@ -201,6 +203,9 @@ class SlackApi:
201
203
  self.channel = channel
202
204
  self.chat_kwargs = chat_kwargs
203
205
 
206
+ self._user_groups_initialized = False
207
+ self.usergroups: list[dict] = []
208
+
204
209
  if init_usergroups:
205
210
  self._initiate_usergroups()
206
211
 
@@ -235,6 +240,7 @@ class SlackApi:
235
240
  )
236
241
 
237
242
  def do_send(c: str, t: str) -> None:
243
+ slack_request.labels("chat.postMessage", "POST").inc()
238
244
  self._sc.chat_postMessage(channel=c, text=t, **self.chat_kwargs)
239
245
 
240
246
  try:
@@ -283,6 +289,8 @@ class SlackApi:
283
289
 
284
290
  channels_found = self.get_channels_by_names(self.channel)
285
291
  [channel_id] = [k for k in channels_found if channels_found[k] == self.channel]
292
+ slack_request.labels("conversations.info", "GET").inc()
293
+
286
294
  info = self._sc.conversations_info(channel=channel_id)
287
295
  if not info.data["channel"]["is_member"]: # type: ignore[call-overload]
288
296
  self._sc.conversations_join(channel=channel_id)
@@ -300,17 +308,26 @@ class SlackApi:
300
308
  :raises slack_sdk.errors.SlackApiError: if unsuccessful response from
301
309
  Slack API
302
310
  """
311
+ slack_request.labels("usergroups.list", "GET").inc()
312
+
303
313
  result = self._sc.usergroups_list(include_users=True)
304
314
  self.usergroups = result["usergroups"]
315
+ self._user_groups_initialized = True
305
316
 
306
317
  def get_usergroup(self, handle: str) -> dict[str, Any]:
318
+ if not self._user_groups_initialized:
319
+ self._initiate_usergroups()
307
320
  usergroup = [g for g in self.usergroups if g["handle"] == handle]
308
321
  if len(usergroup) != 1:
309
322
  raise UsergroupNotFoundException(handle)
310
323
  return usergroup[0]
311
324
 
312
325
  def create_usergroup(self, handle: str) -> str:
326
+ slack_request.labels("usergroups.create", "POST").inc()
327
+
313
328
  response = self._sc.usergroups_create(name=handle, handle=handle)
329
+ # Invalidate the usergroups list cache
330
+ self._user_groups_initialized = False
314
331
  return response["usergroup"]["id"]
315
332
 
316
333
  def update_usergroup(
@@ -326,6 +343,8 @@ class SlackApi:
326
343
  :raises slack_sdk.errors.SlackApiError: if unsuccessful response from
327
344
  Slack API
328
345
  """
346
+ slack_request.labels("usergroups.update", "POST").inc()
347
+
329
348
  self._sc.usergroups_update(
330
349
  usergroup=id, channels=channels_list, description=description
331
350
  )
@@ -346,6 +365,8 @@ class SlackApi:
346
365
  users_list = [self.get_random_deleted_user()]
347
366
 
348
367
  try:
368
+ slack_request.labels("usergroups.users.update", "POST").inc()
369
+
349
370
  self._sc.usergroups_users_update(usergroup=id, users=users_list)
350
371
  except SlackApiError as e:
351
372
  # Slack can throw an invalid_users error when emptying groups, but
@@ -374,6 +395,8 @@ class SlackApi:
374
395
  :raises UserNotFoundException: if the Slack user is not found
375
396
  """
376
397
  try:
398
+ slack_request.labels("users.lookupByEmail", "GET").inc()
399
+
377
400
  result = self._sc.users_lookupByEmail(email=f"{user_name}@{mail_address}")
378
401
  except SlackApiError as e:
379
402
  if e.response["error"] == "users_not_found":
@@ -433,6 +456,8 @@ class SlackApi:
433
456
  additional_kwargs.update(method_config)
434
457
 
435
458
  while True:
459
+ slack_request.labels(f"{api_key}.list", "GET").inc()
460
+
436
461
  result = self._sc.api_call(
437
462
  "{}.list".format(api_key), http_verb="GET", params=additional_kwargs
438
463
  )
@@ -473,6 +498,8 @@ class SlackApi:
473
498
  responses = []
474
499
  keep_fetching = True
475
500
  while True:
501
+ slack_request.labels("conversations.history", "GET").inc()
502
+
476
503
  response = self._sc.conversations_history(
477
504
  cursor=cursor, channel=self.channel, **self.chat_kwargs
478
505
  )