qontract-reconcile 0.10.1rc758__py3-none-any.whl → 0.10.1rc760__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.1rc758
3
+ Version: 0.10.1rc760
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
@@ -395,17 +395,19 @@ reconcile/rhidp/sso_client/base.py,sha256=EfQ2ewcOKh5idg46UKAkY6z0m_nGQfvnQKffa2
395
395
  reconcile/rhidp/sso_client/integration.py,sha256=kA8g7c38ZBSdrRtyfEqy_WgSreD1PbwY7ZIN-3tZRPc,2221
396
396
  reconcile/rhidp/sso_client/metrics.py,sha256=Tq7tSOsqL3XdcPUdozxqzSPIodUeOV87UCTqpuuqqhw,1013
397
397
  reconcile/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
398
- reconcile/saas_auto_promotions_manager/integration.py,sha256=h1RS-rE8_UaVoLV4c9NyfNfjzvHLQcEVwVN_2UxT7T8,6843
398
+ reconcile/saas_auto_promotions_manager/integration.py,sha256=shWQ--FWfeh_1rHJUwOWDiZWnvzKxYJYuRUIGQv22RI,6759
399
+ reconcile/saas_auto_promotions_manager/meta.py,sha256=2b44ik-qpACNtW72QlDa2YOQqxeN8FHZfLPDmKoH3Rg,161
399
400
  reconcile/saas_auto_promotions_manager/publisher.py,sha256=psrthZGgCQDUO3rwQjKSBMlwcTgfij6sxdebGuxkNv4,2739
400
401
  reconcile/saas_auto_promotions_manager/s3_exporter.py,sha256=IKlVWZmiPnvl7sKeF6JgAlhXZe5CovKTxQc0SNkNSx4,2583
401
402
  reconcile/saas_auto_promotions_manager/subscriber.py,sha256=cLhPlkT71J2LIice3SLmH1WpsqzV46gd0peMxrnqyRw,7452
402
403
  reconcile/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
404
+ reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=jgfgKv4UTYFxtDao_JwNEGEKmu4GpeMm5vbaat0289c,1225
403
405
  reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request.py,sha256=BeAJWLow7b4HQyZ9zz398sQkPeIz8chpMkCts2NU27c,1282
404
- reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py,sha256=Z-tUicItSMWq4WoAU59WTYChtOeS2FShD_bxrhNzsgc,7069
406
+ reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py,sha256=IRLHdz-XMn-rIb0jFddYkIuN9c7FC6InJZ61c9fVjvI,6193
405
407
  reconcile/saas_auto_promotions_manager/merge_request_manager/metrics.py,sha256=sdHp71Wl87tFM-Z_QvqvdHhyrppFLGi4ekksCi_e_bs,977
406
408
  reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py,sha256=x8Gg-YjEFWEeDPJH3Y8SrfcJbwhLuAqCz4kIhfEyaaA,7060
407
- reconcile/saas_auto_promotions_manager/merge_request_manager/reconciler.py,sha256=nst5ZynEQs9Hy9Z2DAjN8_ALIS_5XnrbJh7KG4YVRrE,7789
408
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=Z2G4wMQB3DLTuMlH_MGURGR4uLyh-6RnksQJzdym5VQ,6960
409
+ reconcile/saas_auto_promotions_manager/merge_request_manager/reconciler.py,sha256=KZVAkFJR75Qu7-feV4mzg1S8ua-pkbuu1oC7PebSpDs,7801
410
+ reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=iu0wMyyEvro5r5SBJVN3HGmVSIcxTyLN0Xxx3mhbYXE,7066
409
411
  reconcile/saas_auto_promotions_manager/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
410
412
  reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=ZNxwqp9kdUSoxb3kTdM4KrtPyd3V5O4jMfjrVT2IJfs,7605
411
413
  reconcile/skupper_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -437,7 +439,7 @@ reconcile/terraform_init/integration.py,sha256=xcFKTc_or3xB3kE_I3OECNkkgbwALIwwd
437
439
  reconcile/terraform_init/merge_request.py,sha256=3CYtgSd7Q9zjKg4wsDz437EPCRfGeZZ8fZ0Y-ChKXJY,1475
438
440
  reconcile/terraform_init/merge_request_manager.py,sha256=fMcT6hbdEF3nFATJpvr8BedvQHq_MzFkgVJSloBNwOQ,3101
439
441
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
440
- reconcile/test/conftest.py,sha256=rQousYrxUz-EwAIbsYO6bIwR1B4CrOz9y_zaUVo2lfI,4466
442
+ reconcile/test/conftest.py,sha256=0pO4UxFeBALKbL9gwemyap0VkbPR8n5TtZbf5c9pSv0,4303
441
443
  reconcile/test/fixtures.py,sha256=9SDWAUlSd1rCx7z3GhULHcpr-I6FyCsXxaFAZIqYQsQ,591
442
444
  reconcile/test/test_acs_notifiers.py,sha256=xf3WL6q6V7KQdTVSx6YI-pa4yzOX3mkvIJomgPUc3Mw,12746
443
445
  reconcile/test/test_acs_policies.py,sha256=8pwnXpAO-0OI-6oubjf_oPPlpZjVldeZfJJ9uhsNMWM,17579
@@ -455,7 +457,7 @@ reconcile/test/test_cli.py,sha256=qx_iBwh4Z-YkK3sbjK1wEziPTgn060EN-baf9DNvR3k,10
455
457
  reconcile/test/test_closedbox_endpoint_monitoring.py,sha256=isMHYwRWMFARU2nbJgbl69kD6H0eA86noCM4MPVI1fo,7151
456
458
  reconcile/test/test_dashdotdb_dora.py,sha256=MfHGAsX2eSQSvBVt9_1Sah3aQKNJBXA9Iu86X0NWD6c,7705
457
459
  reconcile/test/test_database_access_manager.py,sha256=-9fYo8wMNhbJUTK_bd7g_fS5zYsAlqQ0rBDDYBMZvZQ,19595
458
- reconcile/test/test_deadmanssnitch.py,sha256=qtn1zwWgIQYw5JULLPvDLaj0GWiecYnvky0HcuETjdo,9843
460
+ reconcile/test/test_deadmanssnitch.py,sha256=YAf8wlZoEC60Ul7UA6Y6XqwnZ1yqf07J15ABqeLpqW4,9835
459
461
  reconcile/test/test_gabi_authorized_users.py,sha256=6XnV5Q9inxP81ktGMVKyWucjBTUj8Imy2L0HG3YHyUE,2496
460
462
  reconcile/test/test_gcr_mirror.py,sha256=A0y8auKZzr62-mGoxSQ__JnN0-ijZUltzjwR5miBgso,490
461
463
  reconcile/test/test_github_org.py,sha256=j3KeB4OnSln1gm2hidce49xdMru-j75NS3cM-AEgzZc,4511
@@ -513,7 +515,7 @@ reconcile/test/test_terraform_users.py,sha256=XOAfGvITCJPI1LTlISmHbA4ONMQMkxYUMT
513
515
  reconcile/test/test_terraform_vpc_peerings.py,sha256=ubcsKh0TrUIwuI1-W3ETIgzsFvzAyeoFmEJFC-IK6JY,20538
514
516
  reconcile/test/test_terraform_vpc_peerings_build_desired_state.py,sha256=DAfpb12I0PlqnuVUHK2vh4LH4d1OylT3H2GE_3TGZZI,47852
515
517
  reconcile/test/test_three_way_diff_strategy.py,sha256=2fjEqE2w4pIzKq18PRcADTSe01aGwsZfMGloU8xfNaE,3346
516
- reconcile/test/test_unleash.py,sha256=c1s_FRAZrAzzd3FbZrzHYjJzHELhoxPHBZnEzqsfMQg,6416
518
+ reconcile/test/test_unleash.py,sha256=krPgOVmwTE6lb773040Ely9BPbNYOeOIY0_8BK72dgo,6690
517
519
  reconcile/test/test_utils_jinja2.py,sha256=TpzQlpFnLGzNEZp5WOh0o7AuBiGEktqO4MuwiiJW2YY,3895
518
520
  reconcile/test/test_vault_replication.py,sha256=wlc4jm9f8P641UvvxIFFFc5_unJysNkOVrKJscjhQr0,16867
519
521
  reconcile/test/test_vault_utils.py,sha256=vbJnc89XAuE07qbTuWxHM5o9F6R9SO5aHXA38fwxT7A,1122
@@ -527,6 +529,7 @@ reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py,sh
527
529
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
528
530
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py,sha256=7O3lbk1EmEtUofqGncfiwMYvDPXrkQNPB59zlQ_zXkM,4588
529
531
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py,sha256=Z1IV51OUuzhd-3S8W-k7ixC-fkaglCokn0eakK0Z73s,606
532
+ reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py,sha256=x5DDZog0SA-Z8noxwalZniGtkovaPvR0V9Pqa5QaFFY,2302
530
533
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py,sha256=h8lnorFPZIxTtbaaXGLoiEsBbB4Qj-Mg9BKV62ZqEBQ,2389
531
534
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py,sha256=dcGHzxuafKSxmswSO1qF2WlKaqsmEvtERC6Lb8kDAN0,10019
532
535
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py,sha256=_bzfJzjFJgubu--7wyXIiusUrdbmLtFbHmkbat4SX_M,17828
@@ -597,7 +600,7 @@ reconcile/utils/binary.py,sha256=EsOGg82Y2QJh91SGJE0tYpBKqU0iaaagQVoYONBtQn8,235
597
600
  reconcile/utils/config.py,sha256=aId5zrPjM_84u_T4yTRE_Psu3zo5-5_JCR6_7Wgv5UQ,990
598
601
  reconcile/utils/constants.py,sha256=pOUd97bqZdsAu5RWJ8NUs9cwCY7K9y0eW9VVeJ4fZIU,138
599
602
  reconcile/utils/data_structures.py,sha256=VyKfnlNJTiRvZKNpfgIrjESQ2YgmEpWuPQXT14WA1vI,311
600
- reconcile/utils/deadmanssnitch_api.py,sha256=hkfbfbRAhzLpI39o6Du7FZKVtf4UVJ1OljOQNUkmODM,2478
603
+ reconcile/utils/deadmanssnitch_api.py,sha256=OWwkqZxjLRNNfFrfZt-zJ4H1hm4OHg5EZ6lP55APOZc,2493
601
604
  reconcile/utils/defer.py,sha256=SniUsbgOEs9Pa8JkecLu0F94O63yQPByKXaElDYe0FI,377
602
605
  reconcile/utils/differ.py,sha256=kJmUp9ZffFPSUEviaAw3s9c92ErwRJeHaRexGPai7wA,7643
603
606
  reconcile/utils/disabled_integrations.py,sha256=avdDsFyl_LdTsrPVzlcIhWzT_V4C4MXw1ZC__aOtluE,1126
@@ -651,12 +654,12 @@ reconcile/utils/promtool.py,sha256=kT2rFZSBaRqW7SSHAuYzGZzQxM5Dzk8KW1NnEUYZU_s,2
651
654
  reconcile/utils/quay_api.py,sha256=EuOegpb-7ntEjkKLFwM2Oo4Nw7SyFtmyl3sQ9aXMtrM,8152
652
655
  reconcile/utils/raw_github_api.py,sha256=ZHC-SZuAyRe1zaMoOU7Krt1-zecDxENd9c_NzQYqK9g,2968
653
656
  reconcile/utils/repo_owners.py,sha256=j-pUjc9PuDzq7KpjNLpnhqfU8tUG4nj2WMhFp4ick7g,6629
654
- reconcile/utils/rest_api_base.py,sha256=uXLdXocNmRCxJsYFWOdUNLaTEu-dcxMLJg_CwRefETE,3970
657
+ reconcile/utils/rest_api_base.py,sha256=X5o4idyRCDzwnF5xFwmjyoaHmM1tXSZnykTA54Z7D2Q,4006
655
658
  reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
656
659
  reconcile/utils/secret_reader.py,sha256=2DeYAAQFjUULEKlLw3UDAUoND6gbqvCh9uKPtlc-0us,10403
657
660
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
658
661
  reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,839
659
- reconcile/utils/slack_api.py,sha256=OPmzU6L9rJx2XXDlZkMlxLjOWu17yC-fVCoUItzQrXw,16295
662
+ reconcile/utils/slack_api.py,sha256=C-VThgYRtrRWraq9ZE6hEf1bXrwzRDCDK0uRw3DP6ew,16425
660
663
  reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
661
664
  reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4dA,2287
662
665
  reconcile/utils/state.py,sha256=FK8NLT1xyumuXpYRm0Nk6pWpOE_U6-NovGn6zKCw8vw,16298
@@ -783,8 +786,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
783
786
  tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
784
787
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
785
788
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
786
- qontract_reconcile-0.10.1rc758.dist-info/METADATA,sha256=AV5BARka8wT3N8-xnQvmBspzkrCa7ewkyMWLaQ2xb-s,2382
787
- qontract_reconcile-0.10.1rc758.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
788
- qontract_reconcile-0.10.1rc758.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
789
- qontract_reconcile-0.10.1rc758.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
790
- qontract_reconcile-0.10.1rc758.dist-info/RECORD,,
789
+ qontract_reconcile-0.10.1rc760.dist-info/METADATA,sha256=4WvkJGFrn51U56sSukrjQU-JoWydSXzdq7hg8hzcsoQ,2382
790
+ qontract_reconcile-0.10.1rc760.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
791
+ qontract_reconcile-0.10.1rc760.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
792
+ qontract_reconcile-0.10.1rc760.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
793
+ qontract_reconcile-0.10.1rc760.dist-info/RECORD,,
@@ -18,6 +18,7 @@ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler imp
18
18
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
19
19
  Renderer,
20
20
  )
21
+ from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION
21
22
  from reconcile.saas_auto_promotions_manager.publisher import Publisher
22
23
  from reconcile.saas_auto_promotions_manager.s3_exporter import S3Exporter
23
24
  from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
@@ -34,14 +35,10 @@ from reconcile.typed_queries.saas_files import get_saas_files
34
35
  from reconcile.utils.defer import defer
35
36
  from reconcile.utils.promotion_state import PromotionState
36
37
  from reconcile.utils.secret_reader import create_secret_reader
37
- from reconcile.utils.semver_helper import make_semver
38
38
  from reconcile.utils.state import State, init_state
39
39
  from reconcile.utils.unleash import get_feature_toggle_state
40
40
  from reconcile.utils.vcs import VCS
41
41
 
42
- QONTRACT_INTEGRATION = "saas-auto-promotions-manager"
43
- QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
44
-
45
42
 
46
43
  class SaasAutoPromotionsManager:
47
44
  def __init__(
@@ -0,0 +1,28 @@
1
+ from collections import defaultdict
2
+ from collections.abc import Iterable
3
+
4
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
5
+ Promotion,
6
+ )
7
+ from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
8
+
9
+
10
+ class DesiredState:
11
+ def __init__(self, subscribers: Iterable[Subscriber]) -> None:
12
+ self.content_hash_to_subscriber: dict[str, list[Subscriber]] = {}
13
+ subscribers_per_channel_combo: dict[str, list[Subscriber]] = defaultdict(list)
14
+ for subscriber in subscribers:
15
+ channel_combo = ",".join([c.name for c in subscriber.channels])
16
+ subscribers_per_channel_combo[channel_combo].append(subscriber)
17
+
18
+ desired_promotions: list[Promotion] = []
19
+ for channel_combo, subs in subscribers_per_channel_combo.items():
20
+ combined_content_hash = Subscriber.combined_content_hash(subscribers=subs)
21
+ self.content_hash_to_subscriber[combined_content_hash] = subs
22
+ desired_promotions.append(
23
+ Promotion(
24
+ content_hashes={combined_content_hash},
25
+ channels={channel_combo},
26
+ )
27
+ )
28
+ self.promotions = desired_promotions
@@ -1,9 +1,11 @@
1
1
  import logging
2
- from collections import defaultdict
3
2
  from collections.abc import Iterable
4
3
 
5
4
  from gitlab.exceptions import GitlabGetError
6
5
 
6
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.desired_state import (
7
+ DesiredState,
8
+ )
7
9
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request import (
8
10
  SAPMMR,
9
11
  )
@@ -21,7 +23,6 @@ from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser impo
21
23
  )
22
24
  from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
23
25
  Addition,
24
- Promotion,
25
26
  Reconciler,
26
27
  )
27
28
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
@@ -66,33 +67,12 @@ class MergeRequestManagerV2:
66
67
  self._mr_parser = mr_parser
67
68
  self._renderer = renderer
68
69
  self._reconciler = reconciler
69
- self._content_hash_to_subscriber: dict[str, list[Subscriber]] = {}
70
70
  self._sapm_mrs: list[SAPMMR] = []
71
71
 
72
- def _aggregate_desired_state(
73
- self, subscribers: Iterable[Subscriber]
74
- ) -> list[Promotion]:
75
- subscribers_per_channel_combo: dict[str, list[Subscriber]] = defaultdict(list)
76
- for subscriber in subscribers:
77
- channel_combo = ",".join([c.name for c in subscriber.channels])
78
- subscribers_per_channel_combo[channel_combo].append(subscriber)
79
-
80
- desired_promotions: list[Promotion] = []
81
- for channel_combo, subs in subscribers_per_channel_combo.items():
82
- combined_content_hash = Subscriber.combined_content_hash(subscribers=subs)
83
- self._content_hash_to_subscriber[combined_content_hash] = subs
84
- desired_promotions.append(
85
- Promotion(
86
- content_hashes={combined_content_hash},
87
- channels={channel_combo},
88
- )
89
- )
90
- return desired_promotions
91
-
92
72
  def _render_mr(self, addition: Addition) -> None:
93
73
  subs: list[Subscriber] = []
94
74
  for content_hash in addition.content_hashes:
95
- subs.extend(self._content_hash_to_subscriber[content_hash])
75
+ subs.extend(self._desired_state.content_hash_to_subscriber[content_hash])
96
76
  content_by_path: dict[str, str] = {}
97
77
  has_error = False
98
78
  for sub in subs:
@@ -151,11 +131,12 @@ class MergeRequestManagerV2:
151
131
 
152
132
  def reconcile(self, subscribers: Iterable[Subscriber]) -> None:
153
133
  current_state = self._mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
154
- desired_state = self._aggregate_desired_state(subscribers=subscribers)
134
+ desired_state = DesiredState(subscribers=subscribers)
135
+ self._desired_state = desired_state
155
136
 
156
137
  diff = self._reconciler.reconcile(
157
138
  batch_limit=BATCH_SIZE_LIMIT,
158
- desired_promotions=desired_state,
139
+ desired_promotions=desired_state.promotions,
159
140
  open_mrs=current_state,
160
141
  )
161
142
  parallel_open_mrs = (
@@ -14,7 +14,7 @@ class Reason(Enum):
14
14
  NEW_BATCH = "Closing this MR in favor of a new batch MR."
15
15
 
16
16
 
17
- @dataclass
17
+ @dataclass(order=True)
18
18
  class Promotion:
19
19
  content_hashes: set[str]
20
20
  channels: set[str]
@@ -13,13 +13,14 @@ from reconcile.gql_definitions.common.saas_files import (
13
13
  from reconcile.gql_definitions.fragments.saas_target_namespace import (
14
14
  SaasTargetNamespace,
15
15
  )
16
+ from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION_VERSION
16
17
  from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
17
18
  from reconcile.utils.ruamel import create_ruamel_instance
18
19
 
19
20
  PROMOTION_DATA_SEPARATOR = (
20
21
  "**SAPM Data - DO NOT MANUALLY CHANGE ANYTHING BELOW THIS LINE**"
21
22
  )
22
- SAPM_VERSION = "2.1.3"
23
+ SAPM_VERSION = QONTRACT_INTEGRATION_VERSION
23
24
  CONTENT_HASHES = "content_hashes"
24
25
  CHANNELS_REF = "channels"
25
26
  IS_BATCHABLE = "is_batchable"
@@ -0,0 +1,4 @@
1
+ from reconcile.utils.semver_helper import make_semver
2
+
3
+ QONTRACT_INTEGRATION = "saas-auto-promotions-manager"
4
+ QONTRACT_INTEGRATION_VERSION = make_semver(2, 1, 4)
@@ -12,10 +12,10 @@ from typing import (
12
12
  )
13
13
  from unittest.mock import create_autospec
14
14
 
15
- import httpretty as _httpretty
16
15
  import pytest
17
16
  from pydantic import BaseModel
18
17
  from pydantic.error_wrappers import ValidationError
18
+ from pytest_httpserver import HTTPServer
19
19
 
20
20
  from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
21
21
  from reconcile.test.fixtures import Fixtures
@@ -29,13 +29,6 @@ def patch_sleep(mocker):
29
29
  yield mocker.patch.object(time, "sleep")
30
30
 
31
31
 
32
- @pytest.fixture()
33
- def httpretty():
34
- with _httpretty.enabled(allow_net_connect=False):
35
- _httpretty.reset()
36
- yield _httpretty
37
-
38
-
39
32
  @pytest.fixture
40
33
  def secret_reader(mocker) -> None:
41
34
  mock_secretreader = mocker.patch(
@@ -153,19 +146,18 @@ def gql_api_builder() -> Callable[[Optional[Mapping]], GqlApi]:
153
146
 
154
147
 
155
148
  @pytest.fixture
156
- def set_httpretty_responses_based_on_fixture(httpretty: _httpretty) -> Callable:
157
- """Create httpretty responses based fixture files."""
149
+ def set_httpserver_responses_based_on_fixture(httpserver: HTTPServer) -> Callable:
150
+ """Create httpserver responses based fixture files."""
158
151
 
159
- def _(url: str, fx: Fixtures, paths: Iterable[str]) -> None:
152
+ def _(fx: Fixtures, paths: Iterable[str]) -> None:
160
153
  for path in paths:
161
154
  for method in ["get", "post", "put", "patch", "delete"]:
162
- method_file = Path(fx.path(path)) / f"{method}.json"
155
+ method_file = Path(fx.path(path.lstrip("/"))) / f"{method}.json"
163
156
  if method_file.exists():
164
- httpretty.register_uri(
165
- getattr(httpretty, method.upper()),
166
- f"{url}/{path}",
167
- body=method_file.read_text(),
168
- content_type="text/json",
157
+ httpserver.expect_oneshot_request(
158
+ path, method=method
159
+ ).respond_with_data(
160
+ method_file.read_text(), content_type="text/json"
169
161
  )
170
162
 
171
163
  return _
@@ -0,0 +1,60 @@
1
+ from collections.abc import Callable
2
+
3
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.desired_state import (
4
+ DesiredState,
5
+ )
6
+ from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
7
+
8
+ from .data_keys import (
9
+ CHANNEL,
10
+ )
11
+
12
+
13
+ def test_desired_state_empty() -> None:
14
+ desired_state = DesiredState(subscribers=[])
15
+ assert desired_state.promotions == []
16
+
17
+
18
+ def test_desired_state_single_subscriber(
19
+ subscriber_builder: Callable[..., Subscriber],
20
+ ) -> None:
21
+ subscriber = subscriber_builder({})
22
+ desired_state = DesiredState(subscribers=[subscriber])
23
+ assert len(desired_state.promotions) == 1
24
+ assert desired_state.promotions[0].content_hashes == {
25
+ Subscriber.combined_content_hash([subscriber])
26
+ }
27
+
28
+
29
+ def test_desired_state_multiple_subscribers_same_channel_combo(
30
+ subscriber_builder: Callable[..., Subscriber],
31
+ ) -> None:
32
+ subscriber_a = subscriber_builder({CHANNEL: ["channel-a", "channel-b"]})
33
+ subscriber_a.desired_ref = "ref-a"
34
+ subscriber_b = subscriber_builder({CHANNEL: ["channel-a", "channel-b"]})
35
+ subscriber_b.desired_ref = "ref-b"
36
+ desired_state = DesiredState(subscribers=[subscriber_a, subscriber_b])
37
+ assert len(desired_state.promotions) == 1
38
+ assert desired_state.promotions[0].content_hashes == {
39
+ Subscriber.combined_content_hash([subscriber_a, subscriber_b]),
40
+ }
41
+
42
+
43
+ def test_desired_state_multiple_subscribers_different_channel_combo(
44
+ subscriber_builder: Callable[..., Subscriber],
45
+ ) -> None:
46
+ subscriber_a = subscriber_builder({CHANNEL: ["channel-a", "channel-b"]})
47
+ subscriber_a.desired_ref = "ref-a"
48
+ subscriber_b = subscriber_builder({CHANNEL: ["channel-a", "channel-b"]})
49
+ subscriber_b.desired_ref = "ref-b"
50
+ subscriber_c = subscriber_builder({CHANNEL: ["channel-b", "channel-c"]})
51
+ subscriber_c.desired_ref = "ref-c"
52
+ desired_state = DesiredState(subscribers=[subscriber_a, subscriber_b, subscriber_c])
53
+ sorted_promotions = sorted(desired_state.promotions)
54
+ assert len(desired_state.promotions) == 2
55
+ assert sorted_promotions[0].content_hashes == {
56
+ Subscriber.combined_content_hash([subscriber_a, subscriber_b]),
57
+ }
58
+ assert sorted_promotions[1].content_hashes == {
59
+ Subscriber.combined_content_hash([subscriber_c]),
60
+ }
@@ -20,7 +20,7 @@ from reconcile.utils.deadmanssnitch_api import (
20
20
 
21
21
 
22
22
  @pytest.fixture
23
- def deadmanssnitch_api() -> MockerFixture:
23
+ def deadmanssnitch_api() -> MagicMock:
24
24
  return create_autospec(DeadMansSnitchApi)
25
25
 
26
26
 
@@ -55,7 +55,7 @@ def secret_reader(mocker: MockerFixture) -> MockerFixture:
55
55
 
56
56
  def test_get_current_state(
57
57
  secret_reader: MagicMock,
58
- deadmanssnitch_api: MockerFixture,
58
+ deadmanssnitch_api: MagicMock,
59
59
  mocker: MockerFixture,
60
60
  deadmanssnitch_settings: DeadMansSnitchSettingsV1,
61
61
  ) -> None:
@@ -1,8 +1,8 @@
1
- import json
2
1
  import os
2
+ from collections.abc import Callable
3
3
 
4
- import httpretty
5
4
  import pytest
5
+ from pytest_httpserver import HTTPServer
6
6
  from UnleashClient.features import Feature
7
7
 
8
8
  import reconcile.utils.unleash
@@ -22,6 +22,75 @@ def reset_client():
22
22
  reconcile.utils.unleash.client = None
23
23
 
24
24
 
25
+ def _setup_unleash_httpserver(features: dict, httpserver: HTTPServer) -> HTTPServer:
26
+ httpserver.expect_request("/client/features").respond_with_json(features)
27
+ httpserver.expect_request("/client/register", method="post").respond_with_data(
28
+ status=202
29
+ )
30
+ return httpserver
31
+
32
+
33
+ @pytest.fixture
34
+ def setup_unleash_disable_cluster_strategy(httpserver: HTTPServer):
35
+ def _(enabled: bool) -> HTTPServer:
36
+ features = {
37
+ "version": 2,
38
+ "features": [
39
+ {
40
+ "strategies": [
41
+ {
42
+ "name": "disableCluster",
43
+ "constraints": [],
44
+ "parameters": {"cluster_name": "foo"},
45
+ },
46
+ ],
47
+ "impressionData": False,
48
+ "enabled": enabled,
49
+ "name": "test-strategies",
50
+ "description": "",
51
+ "project": "default",
52
+ "stale": False,
53
+ "type": "release",
54
+ "variants": [],
55
+ }
56
+ ],
57
+ }
58
+ return _setup_unleash_httpserver(features, httpserver)
59
+
60
+ return _
61
+
62
+
63
+ @pytest.fixture
64
+ def setup_unleash_enable_cluster_strategy(httpserver: HTTPServer):
65
+ def _(enabled: bool) -> HTTPServer:
66
+ features = {
67
+ "version": 2,
68
+ "features": [
69
+ {
70
+ "strategies": [
71
+ {
72
+ "name": "enableCluster",
73
+ "constraints": [],
74
+ "parameters": {"cluster_name": "enabled-cluster"},
75
+ },
76
+ ],
77
+ "impressionData": False,
78
+ "enabled": enabled,
79
+ "name": "test-strategies",
80
+ "description": "",
81
+ "project": "default",
82
+ "stale": False,
83
+ "type": "release",
84
+ "variants": [],
85
+ }
86
+ ],
87
+ }
88
+
89
+ return _setup_unleash_httpserver(features, httpserver)
90
+
91
+ return _
92
+
93
+
25
94
  def test__get_unleash_api_client(mocker):
26
95
  mocked_unleash_client = mocker.patch(
27
96
  "reconcile.utils.unleash.UnleashClient",
@@ -108,42 +177,12 @@ def test_get_feature_toggles(mocker, monkeypatch):
108
177
  assert toggles["bar"] == "enabled"
109
178
 
110
179
 
111
- def setup_unleash_disable_cluster_strategy_httpretty(enabled: bool):
112
- features = {
113
- "version": 2,
114
- "features": [
115
- {
116
- "strategies": [
117
- {
118
- "name": "disableCluster",
119
- "constraints": [],
120
- "parameters": {"cluster_name": "foo"},
121
- },
122
- ],
123
- "impressionData": False,
124
- "enabled": enabled,
125
- "name": "test-strategies",
126
- "description": "",
127
- "project": "default",
128
- "stale": False,
129
- "type": "release",
130
- "variants": [],
131
- }
132
- ],
133
- }
134
-
135
- feature_param = (httpretty.GET, "http://unleash/api/client/features")
136
- httpretty.register_uri(*feature_param, body=json.dumps(features), status=200)
137
-
138
- register_param = (httpretty.POST, "http://unleash/api/client/register")
139
- httpretty.register_uri(*register_param, status=202)
140
-
141
-
142
- @httpretty.activate(allow_net_connect=False)
143
- def test_get_feature_toggle_state_with_strategy(reset_client):
144
- os.environ["UNLEASH_API_URL"] = "http://unleash/api"
180
+ def test_get_feature_toggle_state_with_strategy(
181
+ reset_client: None, setup_unleash_disable_cluster_strategy: Callable
182
+ ):
183
+ httpserver = setup_unleash_disable_cluster_strategy(True)
184
+ os.environ["UNLEASH_API_URL"] = httpserver.url_for("/")
145
185
  os.environ["UNLEASH_CLIENT_ACCESS_TOKEN"] = "bar"
146
- setup_unleash_disable_cluster_strategy_httpretty(True)
147
186
  assert not get_feature_toggle_state(
148
187
  "test-strategies", context={"cluster_name": "foo"}
149
188
  )
@@ -151,53 +190,24 @@ def test_get_feature_toggle_state_with_strategy(reset_client):
151
190
  _shutdown_client()
152
191
 
153
192
 
154
- @httpretty.activate(allow_net_connect=False)
155
- def test_get_feature_toggle_state_disabled_with_strategy(reset_client):
156
- os.environ["UNLEASH_API_URL"] = "http://unleash/api"
193
+ def test_get_feature_toggle_state_disabled_with_strategy(
194
+ reset_client: None, setup_unleash_disable_cluster_strategy: Callable
195
+ ):
196
+ httpserver = setup_unleash_disable_cluster_strategy(False)
197
+ os.environ["UNLEASH_API_URL"] = httpserver.url_for("/")
157
198
  os.environ["UNLEASH_CLIENT_ACCESS_TOKEN"] = "bar"
158
- setup_unleash_disable_cluster_strategy_httpretty(False)
159
199
  assert not get_feature_toggle_state(
160
200
  "test-strategies", context={"cluster_name": "bar"}
161
201
  )
162
202
  _shutdown_client()
163
203
 
164
204
 
165
- def setup_unleash_enable_cluster_strategy_httpretty(enabled: bool):
166
- features = {
167
- "version": 2,
168
- "features": [
169
- {
170
- "strategies": [
171
- {
172
- "name": "enableCluster",
173
- "constraints": [],
174
- "parameters": {"cluster_name": "enabled-cluster"},
175
- },
176
- ],
177
- "impressionData": False,
178
- "enabled": enabled,
179
- "name": "test-strategies",
180
- "description": "",
181
- "project": "default",
182
- "stale": False,
183
- "type": "release",
184
- "variants": [],
185
- }
186
- ],
187
- }
188
-
189
- feature_param = (httpretty.GET, "http://unleash/api/client/features")
190
- httpretty.register_uri(*feature_param, body=json.dumps(features), status=200)
191
-
192
- register_param = (httpretty.POST, "http://unleash/api/client/register")
193
- httpretty.register_uri(*register_param, status=202)
194
-
195
-
196
- @httpretty.activate(allow_net_connect=False)
197
- def test_get_feature_toggle_state_with_enable_cluster_strategy(reset_client):
198
- os.environ["UNLEASH_API_URL"] = "http://unleash/api"
205
+ def test_get_feature_toggle_state_with_enable_cluster_strategy(
206
+ reset_client: None, setup_unleash_enable_cluster_strategy: Callable
207
+ ):
208
+ httpserver = setup_unleash_enable_cluster_strategy(True)
209
+ os.environ["UNLEASH_API_URL"] = httpserver.url_for("/")
199
210
  os.environ["UNLEASH_CLIENT_ACCESS_TOKEN"] = "bar"
200
- setup_unleash_enable_cluster_strategy_httpretty(True)
201
211
  assert get_feature_toggle_state(
202
212
  "test-strategies", context={"cluster_name": "enabled-cluster"}
203
213
  )
@@ -49,10 +49,12 @@ class DeadMansSnitchApi:
49
49
  self.session.close()
50
50
 
51
51
  def get_snitches(self, tags: list[str]) -> list[Snitch]:
52
- full_url = f"{self.url}?tags={','.join(tags)}"
53
52
  logging.debug("Getting snitches for tags:%s", tags)
54
53
  response = self.session.get(
55
- url=full_url, auth=(self.token, ""), timeout=self.timeout
54
+ url=self.url,
55
+ params={"tags": ",".join(tags)},
56
+ auth=(self.token, ""),
57
+ timeout=self.timeout,
56
58
  )
57
59
  response.raise_for_status()
58
60
  snitches = [Snitch(**item) for item in response.json()]
@@ -75,6 +75,7 @@ class ApiBase:
75
75
 
76
76
  def _get(self, url: str) -> dict[str, Any]:
77
77
  response = self.session.get(urljoin(self.host, url), timeout=self.read_timeout)
78
+ response.raise_for_status()
78
79
  return response.json()
79
80
 
80
81
  def _list(
@@ -166,6 +166,7 @@ class SlackApi:
166
166
  api_config: Optional[SlackApiConfig] = None,
167
167
  init_usergroups: bool = True,
168
168
  channel: Optional[str] = None,
169
+ slack_url: Optional[str] = None,
169
170
  **chat_kwargs: Any,
170
171
  ) -> None:
171
172
  """
@@ -187,7 +188,11 @@ class SlackApi:
187
188
  else:
188
189
  self.config = SlackApiConfig()
189
190
 
190
- self._sc = WebClient(token=token, timeout=self.config.timeout)
191
+ self._sc = WebClient(
192
+ token=token,
193
+ timeout=self.config.timeout,
194
+ base_url=slack_url or WebClient.BASE_URL,
195
+ )
191
196
  self._configure_client_retry()
192
197
 
193
198
  self._results: dict[str, Any] = {}