qontract-reconcile 0.10.1rc514__py3-none-any.whl → 0.10.1rc515__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.
Files changed (18) hide show
  1. {qontract_reconcile-0.10.1rc514.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc514.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/RECORD +17 -15
  3. reconcile/saas_auto_promotions_manager/integration.py +28 -3
  4. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +5 -5
  5. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +144 -0
  6. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +13 -9
  7. reconcile/saas_auto_promotions_manager/merge_request_manager/reconciler.py +207 -0
  8. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +9 -6
  9. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +25 -12
  10. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +3 -0
  11. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +51 -153
  12. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +11 -9
  13. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +408 -0
  14. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +23 -88
  15. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_unbatching.py +0 -96
  16. {qontract_reconcile-0.10.1rc514.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/WHEEL +0 -0
  17. {qontract_reconcile-0.10.1rc514.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/entry_points.txt +0 -0
  18. {qontract_reconcile-0.10.1rc514.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/top_level.txt +0 -0
@@ -11,16 +11,22 @@ from gitlab.v4.objects import ProjectMergeRequest
11
11
  from reconcile.gql_definitions.fragments.saas_target_namespace import (
12
12
  SaasTargetNamespace,
13
13
  )
14
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
15
+ SAPM_LABEL,
16
+ )
14
17
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
15
18
  MRParser,
16
19
  OpenMergeRequest,
17
20
  )
21
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
22
+ Diff,
23
+ Reconciler,
24
+ )
18
25
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
19
26
  CHANNELS_REF,
20
27
  CONTENT_HASHES,
21
28
  IS_BATCHABLE,
22
29
  PROMOTION_DATA_SEPARATOR,
23
- SAPM_LABEL,
24
30
  SAPM_VERSION,
25
31
  VERSION_REF,
26
32
  Renderer,
@@ -32,17 +38,16 @@ from reconcile.saas_auto_promotions_manager.subscriber import (
32
38
  from reconcile.utils.vcs import VCS, MRCheckStatus
33
39
 
34
40
  from .data_keys import (
41
+ CHANNEL,
35
42
  DESCRIPTION,
36
43
  HAS_CONFLICTS,
37
44
  LABELS,
38
45
  OPEN_MERGE_REQUESTS,
39
46
  PIPELINE_RESULTS,
47
+ REF,
40
48
  SUBSCRIBER_BATCHABLE,
41
49
  SUBSCRIBER_CHANNELS,
42
50
  SUBSCRIBER_CONTENT_HASH,
43
- SUBSCRIBER_DESIRED_CONFIG_HASHES,
44
- SUBSCRIBER_DESIRED_REF,
45
- SUBSCRIBER_TARGET_NAMESPACE,
46
51
  SUBSCRIBER_TARGET_PATH,
47
52
  )
48
53
 
@@ -96,7 +101,7 @@ def vcs_builder(
96
101
 
97
102
 
98
103
  @pytest.fixture
99
- def mr_parser_builder() -> Callable[[Mapping], MRParser]:
104
+ def mr_parser_builder() -> Callable[[Iterable[OpenMergeRequest]], MRParser]:
100
105
  def builder(data: Iterable[OpenMergeRequest]) -> MRParser:
101
106
  mr_parser = create_autospec(spec=MRParser)
102
107
  mr_parser.retrieve_open_mrs.side_effect = [data]
@@ -105,24 +110,32 @@ def mr_parser_builder() -> Callable[[Mapping], MRParser]:
105
110
  return builder
106
111
 
107
112
 
113
+ @pytest.fixture
114
+ def reconciler_builder() -> Callable[[Diff], Reconciler]:
115
+ def builder(data: Diff) -> Reconciler:
116
+ reconciler = create_autospec(spec=Reconciler)
117
+ reconciler.reconcile.side_effect = [data]
118
+ return reconciler
119
+
120
+ return builder
121
+
122
+
108
123
  @pytest.fixture
109
124
  def subscriber_builder(
110
125
  saas_target_namespace_builder: Callable[..., SaasTargetNamespace],
111
- ):
126
+ ) -> Callable[..., Subscriber]:
112
127
  def builder(data: Mapping) -> Subscriber:
113
128
  subscriber = Subscriber(
114
129
  saas_name="",
115
130
  template_name="",
116
- target_namespace=saas_target_namespace_builder(
117
- data.get(SUBSCRIBER_TARGET_NAMESPACE, {})
118
- ),
131
+ target_namespace=saas_target_namespace_builder({}),
119
132
  ref="",
120
133
  target_file_path=data.get(SUBSCRIBER_TARGET_PATH, ""),
121
134
  use_target_config_hash=True,
122
135
  )
123
- subscriber.desired_hashes = data.get(SUBSCRIBER_DESIRED_CONFIG_HASHES, [])
124
- subscriber.desired_ref = data.get(SUBSCRIBER_DESIRED_REF, "")
125
- for channel in data.get(SUBSCRIBER_CHANNELS, []):
136
+ subscriber.desired_hashes = []
137
+ subscriber.desired_ref = data.get(REF, "")
138
+ for channel in data.get(CHANNEL, []):
126
139
  subscriber.channels.append(
127
140
  Channel(
128
141
  name=channel,
@@ -14,3 +14,6 @@ SUBSCRIBER_BATCHABLE = "sub_batchable"
14
14
  HAS_CONFLICTS = "has_conflicts"
15
15
  SUBSCRIBER_TARGET_NAMESPACE = "subscriber_target_namespace"
16
16
  PIPELINE_RESULTS = "pipeline_results"
17
+
18
+ CHANNEL = "channel"
19
+ REF = "ref"
@@ -1,185 +1,83 @@
1
- from collections.abc import (
2
- Callable,
3
- Iterable,
4
- Mapping,
5
- )
6
- from unittest.mock import create_autospec
1
+ from collections.abc import Callable
2
+ from unittest.mock import call, create_autospec
7
3
 
8
- import pytest
9
4
  from gitlab.v4.objects import ProjectMergeRequest
10
5
 
11
- from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager import (
12
- MergeRequestManager,
6
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
7
+ MergeRequestManagerV2,
13
8
  )
14
9
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
15
10
  MRParser,
16
11
  OpenMergeRequest,
17
12
  )
13
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
14
+ Addition,
15
+ Deletion,
16
+ Diff,
17
+ Reconciler,
18
+ )
18
19
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
19
20
  Renderer,
20
21
  )
21
22
  from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
22
- from reconcile.utils.vcs import VCS
23
-
24
- from .data_keys import (
25
- SUBSCRIBER_CHANNELS,
26
- SUBSCRIBER_DESIRED_CONFIG_HASHES,
27
- SUBSCRIBER_DESIRED_REF,
28
- SUBSCRIBER_TARGET_NAMESPACE,
29
- SUBSCRIBER_TARGET_PATH,
23
+ from reconcile.test.saas_auto_promotions_manager.merge_request_manager.merge_request_manager.data_keys import (
24
+ CHANNEL,
25
+ REF,
30
26
  )
27
+ from reconcile.utils.vcs import VCS
31
28
 
32
29
 
33
- def test_close_old_content(
34
- mr_parser_builder: Callable[[Iterable], MRParser],
35
- renderer: Renderer,
36
- subscriber_builder: Callable[[Mapping], Subscriber],
37
- ):
38
- subscribers = [
39
- subscriber_builder({
40
- SUBSCRIBER_TARGET_NAMESPACE: {"path": "namespace1"},
41
- SUBSCRIBER_TARGET_PATH: "target1",
42
- SUBSCRIBER_DESIRED_REF: "new_sha",
43
- SUBSCRIBER_DESIRED_CONFIG_HASHES: [],
44
- SUBSCRIBER_CHANNELS: ["channel-a", "channel-b"],
45
- })
46
- ]
47
-
48
- open_mrs = [
49
- OpenMergeRequest(
50
- raw=create_autospec(ProjectMergeRequest),
51
- content_hashes="oldcontent",
52
- channels="channel-a,channel-b",
53
- failed_mr_check=False,
54
- is_batchable=True,
55
- )
56
- ]
57
- mr_parser = mr_parser_builder(open_mrs)
30
+ def test_reconcile(
31
+ reconciler_builder: Callable[[Diff], Reconciler],
32
+ subscriber_builder: Callable[..., Subscriber],
33
+ ) -> None:
58
34
  vcs = create_autospec(spec=VCS)
59
- merge_request_manager = MergeRequestManager(
60
- vcs=vcs,
61
- mr_parser=mr_parser,
62
- renderer=renderer,
63
- )
64
- merge_request_manager.housekeeping()
65
- merge_request_manager.create_promotion_merge_requests(subscribers=subscribers)
66
-
67
- # There is an open MR with old content for that subscriber
68
- # Close old content and open new MR with new content
69
- vcs.close_app_interface_mr.assert_called_once()
70
- vcs.open_app_interface_merge_request.assert_called_once()
71
-
72
-
73
- @pytest.mark.parametrize(
74
- "hash_prefix, hash_suffix, channel_prefix, channel_suffix",
75
- [
76
- ("", "", "", ""),
77
- ("hashprefix,", "", "", ""),
78
- ("", ",hashsuffix", "", ""),
79
- ("", "", "channelprefix,", ""),
80
- ("", "", "", ",channelsuffix"),
81
- ("a,", ",b", "c,", ",d"),
82
- ],
83
- )
84
- def test_merge_request_already_opened(
85
- mr_parser_builder: Callable[[Iterable], MRParser],
86
- renderer: Renderer,
87
- subscriber_builder: Callable[[Mapping], Subscriber],
88
- hash_prefix: str,
89
- hash_suffix: str,
90
- channel_prefix: str,
91
- channel_suffix: str,
92
- ):
93
- subscriber_channel = "channel-a"
35
+ mr_parser = create_autospec(spec=MRParser)
36
+ renderer = create_autospec(spec=Renderer)
94
37
  subscribers = [
95
38
  subscriber_builder({
96
- SUBSCRIBER_TARGET_NAMESPACE: {"path": "namespace1"},
97
- SUBSCRIBER_TARGET_PATH: "target1",
98
- SUBSCRIBER_DESIRED_REF: "new_sha",
99
- SUBSCRIBER_DESIRED_CONFIG_HASHES: [],
100
- SUBSCRIBER_CHANNELS: [subscriber_channel],
39
+ CHANNEL: ["chan1,chan2"],
40
+ REF: "hash1",
101
41
  })
102
42
  ]
103
- content_hash = Subscriber.combined_content_hash(subscribers=subscribers)
104
-
105
- open_mrs = [
106
- OpenMergeRequest(
43
+ deletion = Deletion(
44
+ mr=OpenMergeRequest(
107
45
  raw=create_autospec(spec=ProjectMergeRequest),
108
- content_hashes=f"{hash_prefix}{content_hash}{hash_suffix}",
109
- channels=f"{channel_prefix}{subscriber_channel}{channel_suffix}",
110
- is_batchable=True,
46
+ channels=set(),
47
+ content_hashes=set(),
111
48
  failed_mr_check=False,
112
- )
113
- ]
114
- mr_parser = mr_parser_builder(open_mrs)
115
-
116
- vcs = create_autospec(spec=VCS)
117
- merge_request_manager = MergeRequestManager(
118
- vcs=vcs,
119
- mr_parser=mr_parser,
120
- renderer=renderer,
49
+ is_batchable=True,
50
+ ),
51
+ reason="some reason.",
121
52
  )
122
- merge_request_manager.housekeeping()
123
- merge_request_manager.create_promotion_merge_requests(subscribers=subscribers)
124
-
125
- # There is already an open merge request for this subscriber content
126
- # Do not open another one
127
- vcs.close_app_interface_mr.assert_not_called()
128
- vcs.open_app_interface_merge_request.assert_not_called()
129
53
 
130
-
131
- @pytest.mark.parametrize(
132
- "hash_prefix, hash_suffix, channel_prefix, channel_suffix",
133
- [
134
- ("", "", "", ""),
135
- ("hashprefix,", "", "", ""),
136
- ("", ",hashsuffix", "", ""),
137
- ("", "", "channelprefix,", ""),
138
- ("", "", "", ",channelsuffix"),
139
- ("a,", ",b", "c,", ",d"),
140
- ],
141
- )
142
- def test_ignore_unrelated_channels(
143
- mr_parser_builder: Callable[[Iterable], MRParser],
144
- renderer: Renderer,
145
- subscriber_builder: Callable[[Mapping], Subscriber],
146
- hash_prefix: str,
147
- hash_suffix: str,
148
- channel_prefix: str,
149
- channel_suffix: str,
150
- ):
151
- subscribers = [
152
- subscriber_builder({
153
- SUBSCRIBER_TARGET_NAMESPACE: {"path": "namespace1"},
154
- SUBSCRIBER_TARGET_PATH: "target1",
155
- SUBSCRIBER_DESIRED_REF: "new_sha",
156
- SUBSCRIBER_DESIRED_CONFIG_HASHES: [],
157
- SUBSCRIBER_CHANNELS: ["channel-a"],
158
- })
159
- ]
160
- content_hash = Subscriber.combined_content_hash(subscribers=subscribers)
161
-
162
- open_mrs = [
163
- OpenMergeRequest(
164
- raw=create_autospec(spec=ProjectMergeRequest),
165
- content_hashes=f"{hash_prefix}{content_hash}{hash_suffix}",
166
- channels=f"{channel_prefix}other-channel{channel_suffix}",
167
- is_batchable=True,
168
- failed_mr_check=False,
54
+ additions = [
55
+ Addition(
56
+ content_hashes={Subscriber.combined_content_hash(subscribers=subscribers)},
57
+ channels={"chan1,chan2"},
58
+ batchable=True,
169
59
  )
170
60
  ]
171
- mr_parser = mr_parser_builder(open_mrs)
172
61
 
173
- vcs = create_autospec(spec=VCS)
174
- merge_request_manager = MergeRequestManager(
62
+ reconciler = reconciler_builder(
63
+ Diff(
64
+ deletions=[deletion],
65
+ additions=additions,
66
+ )
67
+ )
68
+ manager = MergeRequestManagerV2(
175
69
  vcs=vcs,
176
70
  mr_parser=mr_parser,
71
+ reconciler=reconciler,
177
72
  renderer=renderer,
178
73
  )
179
- merge_request_manager.housekeeping()
180
- merge_request_manager.create_promotion_merge_requests(subscribers=subscribers)
181
74
 
182
- # There is already an open merge request for this subscriber content
183
- # Do not open another one, because the channels do not match
184
- vcs.close_app_interface_mr.assert_not_called()
185
- vcs.open_app_interface_merge_request.assert_called_once()
75
+ manager.reconcile(subscribers=subscribers)
76
+
77
+ assert len(manager._sapm_mrs) == len(additions)
78
+ vcs.close_app_interface_mr.assert_has_calls([
79
+ call(deletion.mr.raw, deletion.reason),
80
+ ])
81
+ vcs.open_app_interface_merge_request.assert_has_calls([
82
+ call(mr) for mr in manager._sapm_mrs
83
+ ])
@@ -6,6 +6,9 @@ from unittest.mock import call
6
6
 
7
7
  from gitlab.v4.objects import ProjectMergeRequest
8
8
 
9
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
10
+ SAPM_LABEL,
11
+ )
9
12
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
10
13
  MRParser,
11
14
  )
@@ -14,7 +17,6 @@ from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer impor
14
17
  CONTENT_HASHES,
15
18
  IS_BATCHABLE,
16
19
  PROMOTION_DATA_SEPARATOR,
17
- SAPM_LABEL,
18
20
  SAPM_VERSION,
19
21
  VERSION_REF,
20
22
  )
@@ -60,17 +62,17 @@ def test_valid_parsing(
60
62
  mr_parser = MRParser(
61
63
  vcs=vcs,
62
64
  )
63
- open_mrs = mr_parser.retrieve_open_mrs()
65
+ open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
64
66
  assert len(open_mrs) == 2
65
67
 
66
68
  assert open_mrs[0].raw == expectd_mrs[0]
67
- assert open_mrs[0].channels == "channel0"
68
- assert open_mrs[0].content_hashes == "hash0"
69
+ assert open_mrs[0].channels == {"channel0"}
70
+ assert open_mrs[0].content_hashes == {"hash0"}
69
71
  assert open_mrs[0].is_batchable
70
72
 
71
73
  assert open_mrs[1].raw == expectd_mrs[1]
72
- assert open_mrs[1].channels == "channel1"
73
- assert open_mrs[1].content_hashes == "hash1"
74
+ assert open_mrs[1].channels == {"channel1"}
75
+ assert open_mrs[1].content_hashes == {"hash1"}
74
76
  assert not open_mrs[1].is_batchable
75
77
 
76
78
 
@@ -107,7 +109,7 @@ def test_labels_filter(
107
109
  mr_parser = MRParser(
108
110
  vcs=vcs,
109
111
  )
110
- open_mrs = mr_parser.retrieve_open_mrs()
112
+ open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
111
113
  assert len(open_mrs) == 1
112
114
  assert open_mrs[0].raw == expectd_mrs[0]
113
115
 
@@ -261,7 +263,7 @@ def test_bad_mrs(
261
263
  ),
262
264
  ]
263
265
 
264
- open_mrs = mr_parser.retrieve_open_mrs()
266
+ open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
265
267
  assert len(open_mrs) == 0
266
268
  vcs.close_app_interface_mr.assert_has_calls(expected_calls) # type: ignore[attr-defined]
267
269
  assert vcs.close_app_interface_mr.call_count == len(expected_calls) # type: ignore[attr-defined]
@@ -299,7 +301,7 @@ def test_remove_duplicates(
299
301
  mr_parser = MRParser(
300
302
  vcs=vcs,
301
303
  )
302
- open_mrs = mr_parser.retrieve_open_mrs()
304
+ open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
303
305
  vcs.close_app_interface_mr.assert_has_calls([ # type: ignore[attr-defined]
304
306
  call(
305
307
  expected_mrs[1],