qontract-reconcile 0.10.1rc765__py3-none-any.whl → 0.10.1rc767__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 (21) hide show
  1. {qontract_reconcile-0.10.1rc765.dist-info → qontract_reconcile-0.10.1rc767.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc765.dist-info → qontract_reconcile-0.10.1rc767.dist-info}/RECORD +21 -20
  3. reconcile/gql_definitions/ldap_groups/roles.py +12 -2
  4. reconcile/ldap_groups/integration.py +36 -23
  5. reconcile/saas_auto_promotions_manager/integration.py +4 -4
  6. reconcile/saas_auto_promotions_manager/merge_request_manager/{reconciler.py → batcher.py} +12 -12
  7. reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py +1 -1
  8. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +7 -6
  9. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +70 -36
  10. reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py +23 -0
  11. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -0
  12. reconcile/saas_auto_promotions_manager/meta.py +1 -1
  13. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +10 -10
  14. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +11 -9
  15. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +44 -5
  16. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +25 -25
  17. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +4 -4
  18. reconcile/utils/internal_groups/models.py +1 -1
  19. {qontract_reconcile-0.10.1rc765.dist-info → qontract_reconcile-0.10.1rc767.dist-info}/WHEEL +0 -0
  20. {qontract_reconcile-0.10.1rc765.dist-info → qontract_reconcile-0.10.1rc767.dist-info}/entry_points.txt +0 -0
  21. {qontract_reconcile-0.10.1rc765.dist-info → qontract_reconcile-0.10.1rc767.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc765
3
+ Version: 0.10.1rc767
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
@@ -308,7 +308,7 @@ reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions
308
308
  reconcile/gql_definitions/jumphosts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
309
309
  reconcile/gql_definitions/jumphosts/jumphosts.py,sha256=gN595lx7K1XsB2AfxDQ911TBVBbCoxibVeujnsGue_Q,2371
310
310
  reconcile/gql_definitions/ldap_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
311
- reconcile/gql_definitions/ldap_groups/roles.py,sha256=RVTDgMExKb2BGq4dVeBYm5p7HMJlbjme-YkUWh8ikkA,2612
311
+ reconcile/gql_definitions/ldap_groups/roles.py,sha256=goGDnkBBFy0mdLsXqL9qlSLPCFd9rwiD1rrsIH-6nZQ,2888
312
312
  reconcile/gql_definitions/ldap_groups/settings.py,sha256=KR6eKqXQWVYZAUEdatL1RCARaTOWl9X-QmxEMVjVNDE,2227
313
313
  reconcile/gql_definitions/membershipsources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
314
314
  reconcile/gql_definitions/membershipsources/roles.py,sha256=d3nv3GLsj_eKgwB1glsiK6smpC4i16WO3dU5rIdRg94,3678
@@ -377,7 +377,7 @@ reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_c
377
377
  reconcile/jenkins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
378
378
  reconcile/jenkins/types.py,sha256=0UlyJxv3KY1WXHkfI_ghUI6FAwRJTL4EwvLg-62tNcg,3001
379
379
  reconcile/ldap_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
380
- reconcile/ldap_groups/integration.py,sha256=iVfR7bRz8up_bf5Q4aVZHuVoUT6U_aHHkcgY4S9RwqA,10113
380
+ reconcile/ldap_groups/integration.py,sha256=Jj4jWp1aypkoweVNQj7QzMtFKQIWeaunLtXZ8KyKOHE,10670
381
381
  reconcile/ocm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
382
382
  reconcile/ocm/types.py,sha256=ibJYvzfAZyyMFkcF1bP8u3rkXciYJRplt_7Z1pKHFh0,2484
383
383
  reconcile/ocm_internal_notifications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -406,19 +406,20 @@ reconcile/rhidp/sso_client/base.py,sha256=EfQ2ewcOKh5idg46UKAkY6z0m_nGQfvnQKffa2
406
406
  reconcile/rhidp/sso_client/integration.py,sha256=kA8g7c38ZBSdrRtyfEqy_WgSreD1PbwY7ZIN-3tZRPc,2221
407
407
  reconcile/rhidp/sso_client/metrics.py,sha256=Tq7tSOsqL3XdcPUdozxqzSPIodUeOV87UCTqpuuqqhw,1013
408
408
  reconcile/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
409
- reconcile/saas_auto_promotions_manager/integration.py,sha256=shWQ--FWfeh_1rHJUwOWDiZWnvzKxYJYuRUIGQv22RI,6759
410
- reconcile/saas_auto_promotions_manager/meta.py,sha256=jzqK6qM2JDdCE5kzkcj1e1TBMAL9f6lM2Hse8yFb8W8,161
409
+ reconcile/saas_auto_promotions_manager/integration.py,sha256=8IXLEvpblgZRr2UPfTsaLZIUDJOdJaoakhqLBkGU_Es,6750
410
+ reconcile/saas_auto_promotions_manager/meta.py,sha256=76Jp50r6Y_KyJoXFfSjrt5YrCtXyg_A4FXXxHYiS3TE,161
411
411
  reconcile/saas_auto_promotions_manager/publisher.py,sha256=psrthZGgCQDUO3rwQjKSBMlwcTgfij6sxdebGuxkNv4,2739
412
412
  reconcile/saas_auto_promotions_manager/s3_exporter.py,sha256=IKlVWZmiPnvl7sKeF6JgAlhXZe5CovKTxQc0SNkNSx4,2583
413
413
  reconcile/saas_auto_promotions_manager/subscriber.py,sha256=NPhlagNF8om7ikrjRlYNSQ2Ra7wgW_3-OlEWapnjtW0,9405
414
414
  reconcile/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
415
- reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=jgfgKv4UTYFxtDao_JwNEGEKmu4GpeMm5vbaat0289c,1225
415
+ reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py,sha256=lHIULPE8nmPN9pJVfZFdD0l48EB20o4cAy4owahJenw,7848
416
+ reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=isY8frVsL3PlcdZmdZ4O0qyp76oczl4DUMX9uMArs5Y,1222
416
417
  reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request.py,sha256=BeAJWLow7b4HQyZ9zz398sQkPeIz8chpMkCts2NU27c,1282
417
- reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py,sha256=IRLHdz-XMn-rIb0jFddYkIuN9c7FC6InJZ61c9fVjvI,6193
418
+ reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py,sha256=-zWK-cqr_8o9ZEnNfD3cfkv3hrrU4HJ4UMzi4J2Hjhw,6223
418
419
  reconcile/saas_auto_promotions_manager/merge_request_manager/metrics.py,sha256=sdHp71Wl87tFM-Z_QvqvdHhyrppFLGi4ekksCi_e_bs,977
419
- reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py,sha256=x8Gg-YjEFWEeDPJH3Y8SrfcJbwhLuAqCz4kIhfEyaaA,7060
420
- reconcile/saas_auto_promotions_manager/merge_request_manager/reconciler.py,sha256=KZVAkFJR75Qu7-feV4mzg1S8ua-pkbuu1oC7PebSpDs,7801
421
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=iu0wMyyEvro5r5SBJVN3HGmVSIcxTyLN0Xxx3mhbYXE,7066
420
+ reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py,sha256=WDpClngRA7o9j9o8WsTDDU42p_xVqkmr6dERxQXBIAA,8579
421
+ reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py,sha256=-qGQOh6Jdp4lomNDij3zWVC0pl6uPHFWS5Woqcp5HQk,410
422
+ reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=ERi-a400C-c0HnAyS6EsvCrsyY3MO4xL8N3A1e04T4M,7087
422
423
  reconcile/saas_auto_promotions_manager/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
423
424
  reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=7LlTQCzYotWCbtYBaNQP81CFlLUOvsyGMBQE-Ha0cKY,7820
424
425
  reconcile/skupper_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -535,15 +536,15 @@ reconcile/test/test_vpc_peerings_validator.py,sha256=dFSmjc_dMN2GqMbntCFpa7PUZmy
535
536
  reconcile/test/test_wrong_region.py,sha256=7KzL7OaICQ9Z3DW27zt_ykMN7_87owAFC-2CYjvGoyA,2138
536
537
  reconcile/test/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
537
538
  reconcile/test/saas_auto_promotions_manager/conftest.py,sha256=4BtuxVZ0Lsmdp6AhG8kbtowkwG7e-pSjIKv35Wm1hI0,5803
538
- reconcile/test/saas_auto_promotions_manager/test_integration_test.py,sha256=x7QgHDMKIoPRY8k2SRSWa7qY3Z5Vabsyh9xWCtMXtfY,1857
539
+ reconcile/test/saas_auto_promotions_manager/test_integration_test.py,sha256=S30eXJSy2Vc3YLbCP7AfLkOiFGUVoKhEvEBL5vwnbfg,1848
539
540
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
540
541
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
541
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py,sha256=HikuDsdDxJ6HW48WY50s_7fmeGr07kcRdBZxgHzwc-0,3514
542
+ reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py,sha256=y_SL9zBr98SlOnvbA9C7a0_dFZrRAbkuR85m_u6ijcI,3520
542
543
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py,sha256=Z1IV51OUuzhd-3S8W-k7ixC-fkaglCokn0eakK0Z73s,606
543
544
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_desired_state.py,sha256=OLtJ11SsznoT39_tudsozcksK0N3TVNv-JWhIrCVaQk,2356
544
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py,sha256=h8lnorFPZIxTtbaaXGLoiEsBbB4Qj-Mg9BKV62ZqEBQ,2389
545
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py,sha256=dcGHzxuafKSxmswSO1qF2WlKaqsmEvtERC6Lb8kDAN0,10019
546
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py,sha256=_bzfJzjFJgubu--7wyXIiusUrdbmLtFbHmkbat4SX_M,17828
545
+ reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py,sha256=5jOrR5flENfqz7c9ADnjm-yPl7DJLTyVZM1ndvm9MVY,2491
546
+ reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py,sha256=aohFc18g9DKt0kK1YypXJrBGE8eGhsVMOGd1KOCzX0k,11677
547
+ reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py,sha256=REBOmIt1hwu9K5zqyQdKIDhma2YUWIKidI7SJ4GB9ZE,17969
547
548
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
548
549
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py,sha256=MjTLg7KwvdIYvxkCzgJG-gByD1fZxaq6c4SI6rWWUu8,552
549
550
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py,sha256=6LMsnbPx5svTF1nL6oQGLTp1NgjLG_tO56c0hbA2Di4,1059
@@ -692,7 +693,7 @@ reconcile/utils/glitchtip/client.py,sha256=KHUNjN8r2J67RxROIpjzs5cKsqbwjzsM_I1TA
692
693
  reconcile/utils/glitchtip/models.py,sha256=_oqZXNkyRTsAnx6tF4WUURSBj0cc9UNS4okOQYfAfB4,6435
693
694
  reconcile/utils/internal_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
694
695
  reconcile/utils/internal_groups/client.py,sha256=abREA8RwXKybXFjCK8CAcCr-iUp2r0tAbIEJ-c-PXws,4538
695
- reconcile/utils/internal_groups/models.py,sha256=jlkH_hyyyuwS0J1IpuS7W1AyQSKQ2QpHelXoH36edbE,2316
696
+ reconcile/utils/internal_groups/models.py,sha256=GSkF6fVcdbl774q51ZFUmNfSJPta03OcV67MgsuBR2E,2326
696
697
  reconcile/utils/jinja2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
697
698
  reconcile/utils/jinja2/extensions.py,sha256=zV_x8MhSHAynKhFnG3fULXrwsm5fUG_88IygZHSnN0o,1284
698
699
  reconcile/utils/jinja2/filters.py,sha256=_kJjdMsY3lGS5PUn4NnpXUQDNrL1IwiKsB-0MhTMGYM,4521
@@ -782,8 +783,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
782
783
  tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
783
784
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
784
785
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
785
- qontract_reconcile-0.10.1rc765.dist-info/METADATA,sha256=jvfPojPDE35EHICj36iXRI8v0jcPR9pHsqy5dacJiVE,2382
786
- qontract_reconcile-0.10.1rc765.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
787
- qontract_reconcile-0.10.1rc765.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
788
- qontract_reconcile-0.10.1rc765.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
789
- qontract_reconcile-0.10.1rc765.dist-info/RECORD,,
786
+ qontract_reconcile-0.10.1rc767.dist-info/METADATA,sha256=JWA4m9u9lod5MIvLOvk3dDfvQrKGG8cmdIdd4tpoU9M,2382
787
+ qontract_reconcile-0.10.1rc767.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
788
+ qontract_reconcile-0.10.1rc767.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
789
+ qontract_reconcile-0.10.1rc767.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
790
+ qontract_reconcile-0.10.1rc767.dist-info/RECORD,,
@@ -33,7 +33,11 @@ fragment AWSAccountSSO on AWSAccount_v1 {
33
33
  query LdapGroupsRolesQuery {
34
34
  roles: roles_v1 {
35
35
  name
36
- ldapGroup
36
+ ldapGroup {
37
+ name
38
+ notes
39
+ membersAreOwners
40
+ }
37
41
  users {
38
42
  org_username
39
43
  }
@@ -58,6 +62,12 @@ class ConfiguredBaseModel(BaseModel):
58
62
  extra=Extra.forbid
59
63
 
60
64
 
65
+ class LdapGroupV1(ConfiguredBaseModel):
66
+ name: str = Field(..., alias="name")
67
+ notes: Optional[str] = Field(..., alias="notes")
68
+ members_are_owners: Optional[bool] = Field(..., alias="membersAreOwners")
69
+
70
+
61
71
  class UserV1(ConfiguredBaseModel):
62
72
  org_username: str = Field(..., alias="org_username")
63
73
 
@@ -72,7 +82,7 @@ class AWSGroupV1(ConfiguredBaseModel):
72
82
 
73
83
  class RoleV1(ConfiguredBaseModel):
74
84
  name: str = Field(..., alias="name")
75
- ldap_group: Optional[str] = Field(..., alias="ldapGroup")
85
+ ldap_group: Optional[LdapGroupV1] = Field(..., alias="ldapGroup")
76
86
  users: list[UserV1] = Field(..., alias="users")
77
87
  user_policies: Optional[list[AWSUserPolicyV1]] = Field(..., alias="user_policies")
78
88
  aws_groups: Optional[list[AWSGroupV1]] = Field(..., alias="aws_groups")
@@ -93,12 +93,12 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
93
93
  desired_groups_for_roles = self.get_desired_groups_for_roles(
94
94
  roles,
95
95
  contact_list=self.settings.contact_list,
96
- owners=[owner],
96
+ default_owners=[owner],
97
97
  )
98
98
  desired_groups_for_aws_roles = self.get_desired_groups_for_aws_roles(
99
99
  roles,
100
100
  contact_list=self.settings.contact_list,
101
- owners=[owner],
101
+ default_owners=[owner],
102
102
  )
103
103
  desired_groups = desired_groups_for_roles + desired_groups_for_aws_roles
104
104
 
@@ -147,7 +147,7 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
147
147
  data = roles_query(query_func, variables={})
148
148
  roles = [role for role in data.roles or []]
149
149
  if duplicates := find_duplicates(
150
- role.ldap_group for role in roles if role.ldap_group
150
+ role.ldap_group.name for role in roles if role.ldap_group
151
151
  ):
152
152
  for dup in duplicates:
153
153
  logging.error(f"{dup} is already in use by another role.")
@@ -155,30 +155,39 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
155
155
  return roles
156
156
 
157
157
  def get_desired_groups_for_roles(
158
- self, roles: Iterable[RoleV1], owners: Iterable[Entity], contact_list: str
158
+ self,
159
+ roles: Iterable[RoleV1],
160
+ default_owners: list[Entity],
161
+ contact_list: str,
159
162
  ) -> list[Group]:
160
163
  """Return the desired rover groups for the given roles."""
161
- return [
162
- Group(
163
- name=role.ldap_group,
164
- description="Persisted App-Interface role. Managed by qontract-reconcile",
165
- display_name=f"{role.ldap_group} (App-Interface))",
166
- members=[
167
- Entity(type=EntityType.USER, id=user.org_username)
168
- for user in role.users
169
- ],
170
- # only owners can modify the group (e.g. add/remove members)
171
- owners=owners,
172
- contact_list=contact_list,
164
+ groups = []
165
+ for role in roles:
166
+ if not role.ldap_group:
167
+ continue
168
+ members = [
169
+ Entity(type=EntityType.USER, id=user.org_username)
170
+ for user in role.users
171
+ ]
172
+ groups.append(
173
+ Group(
174
+ name=role.ldap_group.name,
175
+ description="Persisted App-Interface role. Managed by qontract-reconcile",
176
+ notes=role.ldap_group.notes,
177
+ display_name=f"{role.ldap_group.name} (App-Interface))",
178
+ members=members,
179
+ owners=default_owners
180
+ if not role.ldap_group.members_are_owners
181
+ else default_owners + members,
182
+ contact_list=contact_list,
183
+ )
173
184
  )
174
- for role in roles
175
- if role.ldap_group
176
- ]
185
+ return groups
177
186
 
178
187
  def get_desired_groups_for_aws_roles(
179
188
  self,
180
189
  roles: Iterable[RoleV1],
181
- owners: list[Entity],
190
+ default_owners: Iterable[Entity],
182
191
  contact_list: str,
183
192
  ) -> list[Group]:
184
193
  """Return the desired rover groups for all AWS roles."""
@@ -205,7 +214,7 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
205
214
  for user in role.users
206
215
  ],
207
216
  # only owners can modify the group (e.g. add/remove members)
208
- owners=owners,
217
+ owners=default_owners,
209
218
  contact_list=contact_list,
210
219
  )
211
220
  )
@@ -246,7 +255,9 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
246
255
  logging.info([
247
256
  "create_ldap_group",
248
257
  group_to_add.name,
249
- f"users={', '.join(u.id for u in group_to_add.members)}",
258
+ f"members={', '.join(u.id for u in group_to_add.members)}",
259
+ f"owners={', '.join(u.id for u in group_to_add.owners)}",
260
+ f"notes={group_to_add.notes}",
250
261
  ])
251
262
  if not dry_run:
252
263
  internal_groups_client.create_group(group_to_add)
@@ -266,7 +277,9 @@ class LdapGroupsIntegration(QontractReconcileIntegration[LdapGroupsIntegrationPa
266
277
  logging.info([
267
278
  "update_ldap_group",
268
279
  group_to_update.name,
269
- f"users={', '.join(u.id for u in group_to_update.members)}",
280
+ f"members={', '.join(u.id for u in group_to_update.members)}",
281
+ f"owners={', '.join(u.id for u in group_to_update.owners)}",
282
+ f"notes={group_to_update.notes}",
270
283
  ])
271
284
  if not dry_run:
272
285
  internal_groups_client.update_group(group_to_update)
@@ -6,15 +6,15 @@ from sretoolbox.utils import threaded
6
6
  from reconcile.openshift_saas_deploy import (
7
7
  QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
8
8
  )
9
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
10
+ Batcher,
11
+ )
9
12
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
10
13
  MergeRequestManagerV2,
11
14
  )
12
15
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
13
16
  MRParser,
14
17
  )
15
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
16
- Reconciler,
17
- )
18
18
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
19
19
  Renderer,
20
20
  )
@@ -135,7 +135,7 @@ def init_external_dependencies(
135
135
  mr_parser = MRParser(vcs=vcs)
136
136
  merge_request_manager_v2 = MergeRequestManagerV2(
137
137
  vcs=vcs,
138
- reconciler=Reconciler(),
138
+ reconciler=Batcher(),
139
139
  mr_parser=mr_parser,
140
140
  renderer=Renderer(),
141
141
  )
@@ -3,8 +3,8 @@ from dataclasses import dataclass
3
3
  from enum import Enum
4
4
  from typing import Optional
5
5
 
6
- from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
7
- OpenMergeRequest,
6
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
7
+ OpenBatcherMergeRequest,
8
8
  )
9
9
 
10
10
 
@@ -22,7 +22,7 @@ class Promotion:
22
22
 
23
23
  @dataclass
24
24
  class Deletion:
25
- mr: OpenMergeRequest
25
+ mr: OpenBatcherMergeRequest
26
26
  reason: Reason
27
27
 
28
28
 
@@ -39,18 +39,18 @@ class Diff:
39
39
  additions: list[Addition]
40
40
 
41
41
 
42
- class Reconciler:
42
+ class Batcher:
43
43
  """
44
- The reconciler calculates a Diff. I.e., which MRs need to be opened (Addition)
45
- and which MRs need to be closed (Deletion). The reconciler has no external
46
- dependencies and does not interact with VCS. The reconciler expects to be
44
+ The batcher calculates a Diff. I.e., which MRs need to be opened (Addition)
45
+ and which MRs need to be closed (Deletion). The batcher has no external
46
+ dependencies and does not interact with VCS. The batcher expects to be
47
47
  given the desired state (which promotions do we want) and the current state
48
48
  (the currently open MRs) in order to calculate the Diff.
49
49
  """
50
50
 
51
51
  def __init__(self) -> None:
52
52
  self._desired_promotions: Iterable[Promotion] = []
53
- self._open_mrs: Iterable[OpenMergeRequest] = []
53
+ self._open_mrs: Iterable[OpenBatcherMergeRequest] = []
54
54
 
55
55
  def _unbatch(self, diff: Diff) -> None:
56
56
  """
@@ -61,7 +61,7 @@ class Reconciler:
61
61
  and close the old batched MR. By doing so, we ensure that unrelated MRs are not blocking each other.
62
62
  Unbatched MRs are marked and will never be batched again.
63
63
  """
64
- open_mrs_after_unbatching: list[OpenMergeRequest] = []
64
+ open_mrs_after_unbatching: list[OpenBatcherMergeRequest] = []
65
65
  unbatchable_hashes: set[str] = set()
66
66
  falsely_marked_batchable_hashes: set[str] = set()
67
67
  for mr in self._open_mrs:
@@ -107,7 +107,7 @@ class Reconciler:
107
107
  for promotion in self._desired_promotions:
108
108
  all_desired_content_hashes.update(promotion.content_hashes)
109
109
 
110
- open_mrs_after_deletion: list[OpenMergeRequest] = []
110
+ open_mrs_after_deletion: list[OpenBatcherMergeRequest] = []
111
111
  for mr in self._open_mrs:
112
112
  if mr.content_hashes.issubset(all_desired_content_hashes):
113
113
  open_mrs_after_deletion.append(mr)
@@ -145,7 +145,7 @@ class Reconciler:
145
145
  if not unsubmitted_promotions:
146
146
  return
147
147
 
148
- batch_with_capacity: Optional[OpenMergeRequest] = None
148
+ batch_with_capacity: Optional[OpenBatcherMergeRequest] = None
149
149
  for mr in self._open_mrs:
150
150
  if mr.is_batchable and len(mr.content_hashes) < batch_limit:
151
151
  batch_with_capacity = mr
@@ -194,7 +194,7 @@ class Reconciler:
194
194
  def reconcile(
195
195
  self,
196
196
  desired_promotions: Iterable[Promotion],
197
- open_mrs: Iterable[OpenMergeRequest],
197
+ open_mrs: Iterable[OpenBatcherMergeRequest],
198
198
  batch_limit: int,
199
199
  ) -> Diff:
200
200
  self._open_mrs = open_mrs
@@ -1,7 +1,7 @@
1
1
  from collections import defaultdict
2
2
  from collections.abc import Iterable
3
3
 
4
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
4
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
5
5
  Promotion,
6
6
  )
7
7
  from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
@@ -3,6 +3,10 @@ from collections.abc import Iterable
3
3
 
4
4
  from gitlab.exceptions import GitlabGetError
5
5
 
6
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
7
+ Addition,
8
+ Batcher,
9
+ )
6
10
  from reconcile.saas_auto_promotions_manager.merge_request_manager.desired_state import (
7
11
  DesiredState,
8
12
  )
@@ -21,10 +25,6 @@ from reconcile.saas_auto_promotions_manager.merge_request_manager.metrics import
21
25
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
22
26
  MRParser,
23
27
  )
24
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
25
- Addition,
26
- Reconciler,
27
- )
28
28
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
29
29
  Renderer,
30
30
  )
@@ -61,7 +61,7 @@ class MergeRequestManagerV2:
61
61
  """
62
62
 
63
63
  def __init__(
64
- self, vcs: VCS, mr_parser: MRParser, reconciler: Reconciler, renderer: Renderer
64
+ self, vcs: VCS, mr_parser: MRParser, reconciler: Batcher, renderer: Renderer
65
65
  ):
66
66
  self._vcs = vcs
67
67
  self._mr_parser = mr_parser
@@ -130,7 +130,8 @@ class MergeRequestManagerV2:
130
130
  )
131
131
 
132
132
  def reconcile(self, subscribers: Iterable[Subscriber]) -> None:
133
- current_state = self._mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
133
+ self._mr_parser.fetch_mrs(label=SAPM_LABEL)
134
+ current_state = self._mr_parser.get_open_batcher_mrs()
134
135
  desired_state = DesiredState(subscribers=subscribers)
135
136
  self._desired_state = desired_state
136
137
 
@@ -1,29 +1,25 @@
1
1
  import logging
2
2
  import re
3
- from dataclasses import dataclass
3
+ from collections.abc import Iterable
4
4
 
5
5
  from gitlab.v4.objects import ProjectMergeRequest
6
6
 
7
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
8
+ MRKind,
9
+ OpenBatcherMergeRequest,
10
+ OpenSchedulerMergeRequest,
11
+ )
7
12
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
8
13
  CHANNELS_REF,
9
14
  CONTENT_HASHES,
10
15
  IS_BATCHABLE,
16
+ MR_KIND_REF,
11
17
  PROMOTION_DATA_SEPARATOR,
12
18
  SAPM_VERSION,
13
19
  VERSION_REF,
14
20
  )
15
21
  from reconcile.utils.vcs import VCS, MRCheckStatus
16
22
 
17
-
18
- @dataclass
19
- class OpenMergeRequest:
20
- raw: ProjectMergeRequest
21
- content_hashes: set[str]
22
- channels: set[str]
23
- failed_mr_check: bool
24
- is_batchable: bool
25
-
26
-
27
23
  ITEM_SEPARATOR = ","
28
24
 
29
25
 
@@ -41,6 +37,15 @@ class MRParser:
41
37
  self._content_hash_regex = re.compile(rf"{CONTENT_HASHES}: (.*)$", re.MULTILINE)
42
38
  self._channels_regex = re.compile(rf"{CHANNELS_REF}: (.*)$", re.MULTILINE)
43
39
  self._is_batchable_regex = re.compile(rf"{IS_BATCHABLE}: (.*)$", re.MULTILINE)
40
+ self._mr_kind_regex = re.compile(rf"{MR_KIND_REF}: (.*)$", re.MULTILINE)
41
+ self._open_batcher_mrs: list[OpenBatcherMergeRequest] = []
42
+ self._open_scheduler_mrs: list[OpenSchedulerMergeRequest] = []
43
+
44
+ def get_open_batcher_mrs(self) -> list[OpenBatcherMergeRequest]:
45
+ return self._open_batcher_mrs
46
+
47
+ def get_open_scheduler_mrs(self) -> list[OpenSchedulerMergeRequest]:
48
+ return self._open_scheduler_mrs
44
49
 
45
50
  def _apply_regex(self, pattern: re.Pattern, promotion_data: str) -> str:
46
51
  matches = pattern.search(promotion_data)
@@ -51,15 +56,9 @@ class MRParser:
51
56
  return ""
52
57
  return groups[0]
53
58
 
54
- def _fetch_sapm_managed_open_merge_requests(
55
- self, label: str
56
- ) -> list[ProjectMergeRequest]:
57
- all_open_mrs = self._vcs.get_open_app_interface_merge_requests()
58
- return [mr for mr in all_open_mrs if label in mr.attributes.get("labels")]
59
-
60
- def retrieve_open_mrs(self, label: str) -> list[OpenMergeRequest]:
59
+ def fetch_mrs(self, label: str) -> None:
61
60
  """
62
- This function parses the state and returns a list of valid, parsed open MRs (current state).
61
+ This function parses the state of valid, parsed open MRs (current state).
63
62
  If any issue is encountered during parsing, we consider this MR
64
63
  to be broken and close it. Information we want to parse includes:
65
64
  - SAPM_VERSION -> Close if it doesnt match current version
@@ -68,21 +67,14 @@ class MRParser:
68
67
  - IS_BATCHABLE flag
69
68
  - MR has merge conflicts
70
69
  """
71
- open_mrs: list[OpenMergeRequest] = []
72
- seen: set[tuple[str, str, str]] = set()
73
- for mr in self._fetch_sapm_managed_open_merge_requests(label=label):
70
+ all_open_mrs = self._vcs.get_open_app_interface_merge_requests()
71
+ sapm_mrs = [mr for mr in all_open_mrs if label in mr.attributes.get("labels")]
72
+ open_batcher_mrs: list[ProjectMergeRequest] = []
73
+ open_scheduler_mrs: list[ProjectMergeRequest] = []
74
+
75
+ for mr in sapm_mrs:
74
76
  attrs = mr.attributes
75
77
  desc = attrs.get("description")
76
- has_conflicts = attrs.get("has_conflicts", False)
77
- if has_conflicts:
78
- logging.info(
79
- "Merge-conflict detected. Closing %s",
80
- mr.attributes.get("web_url", "NO_WEBURL"),
81
- )
82
- self._vcs.close_app_interface_mr(
83
- mr, "Closing this MR because of a merge-conflict."
84
- )
85
- continue
86
78
  parts = desc.split(PROMOTION_DATA_SEPARATOR)
87
79
  if not len(parts) == 2:
88
80
  logging.info(
@@ -121,6 +113,49 @@ class MRParser:
121
113
  )
122
114
  continue
123
115
 
116
+ mr_kind_str = self._apply_regex(
117
+ pattern=self._mr_kind_regex, promotion_data=promotion_data
118
+ )
119
+ if not mr_kind_str or mr_kind_str not in {
120
+ MRKind.BATCHER.value,
121
+ MRKind.SCHEDULER.value,
122
+ }:
123
+ logging.info(
124
+ "Bad %s format. Closing %s",
125
+ MR_KIND_REF,
126
+ mr.attributes.get("web_url", "NO_WEBURL"),
127
+ )
128
+ self._vcs.close_app_interface_mr(
129
+ mr, f"Closing this MR because of bad {MR_KIND_REF} format."
130
+ )
131
+ continue
132
+
133
+ mr_kind = MRKind(mr_kind_str)
134
+ if mr_kind == MRKind.BATCHER:
135
+ open_batcher_mrs.append(mr)
136
+ elif mr_kind == MRKind.SCHEDULER:
137
+ open_scheduler_mrs.append(mr)
138
+
139
+ self._handle_open_batcher_mrs(open_batcher_mrs)
140
+
141
+ def _handle_open_batcher_mrs(self, mrs: Iterable[ProjectMergeRequest]) -> None:
142
+ seen: set[tuple[str, str]] = set()
143
+ for mr in mrs:
144
+ attrs = mr.attributes
145
+ desc = attrs.get("description")
146
+ parts = desc.split(PROMOTION_DATA_SEPARATOR)
147
+ promotion_data = parts[1]
148
+ has_conflicts = attrs.get("has_conflicts", False)
149
+ if has_conflicts:
150
+ logging.info(
151
+ "Merge-conflict detected. Closing %s",
152
+ mr.attributes.get("web_url", "NO_WEBURL"),
153
+ )
154
+ self._vcs.close_app_interface_mr(
155
+ mr, "Closing this MR because of a merge-conflict."
156
+ )
157
+ continue
158
+
124
159
  content_hashes = self._apply_regex(
125
160
  pattern=self._content_hash_regex, promotion_data=promotion_data
126
161
  )
@@ -149,7 +184,7 @@ class MRParser:
149
184
  )
150
185
  continue
151
186
 
152
- key = (version_ref, channels_refs, content_hashes)
187
+ key = (channels_refs, content_hashes)
153
188
  if key in seen:
154
189
  logging.info(
155
190
  "Duplicate MR detected. Closing %s",
@@ -178,8 +213,8 @@ class MRParser:
178
213
 
179
214
  mr_check_status = self._vcs.get_gitlab_mr_check_status(mr)
180
215
 
181
- open_mrs.append(
182
- OpenMergeRequest(
216
+ self._open_batcher_mrs.append(
217
+ OpenBatcherMergeRequest(
183
218
  raw=mr,
184
219
  content_hashes=set(content_hashes.split(ITEM_SEPARATOR)),
185
220
  channels=set(channels_refs.split(ITEM_SEPARATOR)),
@@ -187,4 +222,3 @@ class MRParser:
187
222
  is_batchable=is_batchable_str == "True",
188
223
  )
189
224
  )
190
- return open_mrs
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+
4
+ from gitlab.v4.objects import ProjectMergeRequest
5
+
6
+
7
+ class MRKind(Enum):
8
+ BATCHER = "batcher"
9
+ SCHEDULER = "scheduler"
10
+
11
+
12
+ @dataclass
13
+ class OpenBatcherMergeRequest:
14
+ raw: ProjectMergeRequest
15
+ content_hashes: set[str]
16
+ channels: set[str]
17
+ failed_mr_check: bool
18
+ is_batchable: bool
19
+
20
+
21
+ @dataclass
22
+ class OpenSchedulerMergeRequest:
23
+ pass
@@ -25,6 +25,7 @@ CONTENT_HASHES = "content_hashes"
25
25
  CHANNELS_REF = "channels"
26
26
  IS_BATCHABLE = "is_batchable"
27
27
  VERSION_REF = "sapm_version"
28
+ MR_KIND_REF = "kind"
28
29
 
29
30
 
30
31
  class Renderer:
@@ -1,4 +1,4 @@
1
1
  from reconcile.utils.semver_helper import make_semver
2
2
 
3
3
  QONTRACT_INTEGRATION = "saas-auto-promotions-manager"
4
- QONTRACT_INTEGRATION_VERSION = make_semver(2, 2, 0)
4
+ QONTRACT_INTEGRATION_VERSION = make_semver(2, 3, 0)
@@ -8,16 +8,16 @@ from unittest.mock import create_autospec
8
8
  import pytest
9
9
  from gitlab.v4.objects import ProjectMergeRequest
10
10
 
11
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
12
+ Batcher,
13
+ Diff,
14
+ )
11
15
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
12
16
  SAPM_LABEL,
13
17
  )
14
18
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
15
19
  MRParser,
16
- OpenMergeRequest,
17
- )
18
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
19
- Diff,
20
- Reconciler,
20
+ OpenBatcherMergeRequest,
21
21
  )
22
22
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
23
23
  CHANNELS_REF,
@@ -91,8 +91,8 @@ def vcs_builder(
91
91
 
92
92
 
93
93
  @pytest.fixture
94
- def mr_parser_builder() -> Callable[[Iterable[OpenMergeRequest]], MRParser]:
95
- def builder(data: Iterable[OpenMergeRequest]) -> MRParser:
94
+ def mr_parser_builder() -> Callable[[Iterable[OpenBatcherMergeRequest]], MRParser]:
95
+ def builder(data: Iterable[OpenBatcherMergeRequest]) -> MRParser:
96
96
  mr_parser = create_autospec(spec=MRParser)
97
97
  mr_parser.retrieve_open_mrs.side_effect = [data]
98
98
  return mr_parser
@@ -101,9 +101,9 @@ def mr_parser_builder() -> Callable[[Iterable[OpenMergeRequest]], MRParser]:
101
101
 
102
102
 
103
103
  @pytest.fixture
104
- def reconciler_builder() -> Callable[[Diff], Reconciler]:
105
- def builder(data: Diff) -> Reconciler:
106
- reconciler = create_autospec(spec=Reconciler)
104
+ def reconciler_builder() -> Callable[[Diff], Batcher]:
105
+ def builder(data: Diff) -> Batcher:
106
+ reconciler = create_autospec(spec=Batcher)
107
107
  reconciler.reconcile.side_effect = [data]
108
108
  return reconciler
109
109
 
@@ -3,19 +3,21 @@ from unittest.mock import call, create_autospec
3
3
 
4
4
  from gitlab.v4.objects import ProjectMergeRequest
5
5
 
6
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
7
+ Addition,
8
+ Batcher,
9
+ Deletion,
10
+ Diff,
11
+ Reason,
12
+ )
6
13
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
7
14
  MergeRequestManagerV2,
8
15
  )
9
16
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
10
17
  MRParser,
11
- OpenMergeRequest,
12
18
  )
13
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
14
- Addition,
15
- Deletion,
16
- Diff,
17
- Reason,
18
- Reconciler,
19
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
20
+ OpenBatcherMergeRequest,
19
21
  )
20
22
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
21
23
  Renderer,
@@ -29,7 +31,7 @@ from reconcile.utils.vcs import VCS
29
31
 
30
32
 
31
33
  def test_reconcile(
32
- reconciler_builder: Callable[[Diff], Reconciler],
34
+ reconciler_builder: Callable[[Diff], Batcher],
33
35
  subscriber_builder: Callable[..., Subscriber],
34
36
  ) -> None:
35
37
  vcs = create_autospec(spec=VCS)
@@ -42,7 +44,7 @@ def test_reconcile(
42
44
  })
43
45
  ]
44
46
  deletion = Deletion(
45
- mr=OpenMergeRequest(
47
+ mr=OpenBatcherMergeRequest(
46
48
  raw=create_autospec(spec=ProjectMergeRequest),
47
49
  channels=set(),
48
50
  content_hashes=set(),
@@ -12,10 +12,14 @@ from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_
12
12
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
13
13
  MRParser,
14
14
  )
15
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
16
+ MRKind,
17
+ )
15
18
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
16
19
  CHANNELS_REF,
17
20
  CONTENT_HASHES,
18
21
  IS_BATCHABLE,
22
+ MR_KIND_REF,
19
23
  PROMOTION_DATA_SEPARATOR,
20
24
  SAPM_VERSION,
21
25
  VERSION_REF,
@@ -44,6 +48,7 @@ def test_valid_parsing(
44
48
  {CHANNELS_REF}: channel0
45
49
  {CONTENT_HASHES}: hash0
46
50
  {IS_BATCHABLE}: True
51
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
47
52
  """,
48
53
  },
49
54
  {
@@ -55,6 +60,7 @@ def test_valid_parsing(
55
60
  {CHANNELS_REF}: channel1
56
61
  {CONTENT_HASHES}: hash1
57
62
  {IS_BATCHABLE}: False
63
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
58
64
  """,
59
65
  },
60
66
  ]
@@ -62,7 +68,8 @@ def test_valid_parsing(
62
68
  mr_parser = MRParser(
63
69
  vcs=vcs,
64
70
  )
65
- open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
71
+ mr_parser.fetch_mrs(label=SAPM_LABEL)
72
+ open_mrs = mr_parser._open_batcher_mrs
66
73
  assert len(open_mrs) == 2
67
74
 
68
75
  assert open_mrs[0].raw == expectd_mrs[0]
@@ -90,6 +97,7 @@ def test_labels_filter(
90
97
  {CHANNELS_REF}: other-channel
91
98
  {CONTENT_HASHES}: other_hash
92
99
  {IS_BATCHABLE}: True
100
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
93
101
  """,
94
102
  },
95
103
  # This MR should get ignored
@@ -102,6 +110,7 @@ def test_labels_filter(
102
110
  {CHANNELS_REF}: some-channel
103
111
  {CONTENT_HASHES}: some_hash
104
112
  {IS_BATCHABLE}: True
113
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
105
114
  """,
106
115
  },
107
116
  ]
@@ -109,7 +118,8 @@ def test_labels_filter(
109
118
  mr_parser = MRParser(
110
119
  vcs=vcs,
111
120
  )
112
- open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
121
+ mr_parser.fetch_mrs(label=SAPM_LABEL)
122
+ open_mrs = mr_parser.get_open_batcher_mrs()
113
123
  assert len(open_mrs) == 1
114
124
  assert open_mrs[0].raw == expectd_mrs[0]
115
125
 
@@ -128,6 +138,7 @@ def test_bad_mrs(
128
138
  {CHANNELS_REF}: some-channel
129
139
  {CONTENT_HASHES}: hash_1
130
140
  {IS_BATCHABLE}: True
141
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
131
142
  """,
132
143
  },
133
144
  {
@@ -139,6 +150,7 @@ def test_bad_mrs(
139
150
  {CHANNELS_REF}: some-channel
140
151
  {IS_BATCHABLE}: True
141
152
  missing-content-hash-key: some_hash
153
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
142
154
  """,
143
155
  },
144
156
  {
@@ -149,6 +161,7 @@ def test_bad_mrs(
149
161
  {VERSION_REF}: {SAPM_VERSION}
150
162
  {CONTENT_HASHES}: hash_3
151
163
  {IS_BATCHABLE}: True
164
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
152
165
  """,
153
166
  },
154
167
  {
@@ -160,6 +173,7 @@ def test_bad_mrs(
160
173
  {CHANNELS_REF}: some-channel
161
174
  {CONTENT_HASHES}: hash_4
162
175
  {IS_BATCHABLE}: True
176
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
163
177
  """,
164
178
  },
165
179
  {
@@ -173,6 +187,7 @@ def test_bad_mrs(
173
187
  {CHANNELS_REF}: some-channel
174
188
  {CONTENT_HASHES}: hash_5
175
189
  {IS_BATCHABLE}: True
190
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
176
191
  """,
177
192
  },
178
193
  {
@@ -184,6 +199,7 @@ def test_bad_mrs(
184
199
  {CHANNELS_REF}: some-channel
185
200
  {CONTENT_HASHES}: hash_6
186
201
  {IS_BATCHABLE}: True
202
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
187
203
  """,
188
204
  },
189
205
  {
@@ -195,6 +211,7 @@ def test_bad_mrs(
195
211
  bad_channel_ref: some-channel
196
212
  {CONTENT_HASHES}: hash_7
197
213
  {IS_BATCHABLE}: True
214
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
198
215
  """,
199
216
  },
200
217
  {
@@ -206,6 +223,7 @@ def test_bad_mrs(
206
223
  {CHANNELS_REF}: some-channel
207
224
  {CONTENT_HASHES}: hash_8
208
225
  missing-batchable-key
226
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
209
227
  """,
210
228
  },
211
229
  {
@@ -217,6 +235,19 @@ def test_bad_mrs(
217
235
  {CHANNELS_REF}: some-channel
218
236
  {CONTENT_HASHES}: hash_9
219
237
  {IS_BATCHABLE}: Something-non-bool
238
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
239
+ """,
240
+ },
241
+ {
242
+ LABELS: [SAPM_LABEL],
243
+ DESCRIPTION: f"""
244
+ Blabla
245
+ {PROMOTION_DATA_SEPARATOR}
246
+ {VERSION_REF}: {SAPM_VERSION}
247
+ {CHANNELS_REF}: some-channel
248
+ {CONTENT_HASHES}: hash_10
249
+ {IS_BATCHABLE}: True
250
+ {MR_KIND_REF}: blub
220
251
  """,
221
252
  },
222
253
  ]
@@ -261,11 +292,16 @@ def test_bad_mrs(
261
292
  expected_mrs[8],
262
293
  "Closing this MR because of bad is_batchable format.",
263
294
  ),
295
+ call(
296
+ expected_mrs[9],
297
+ "Closing this MR because of bad kind format.",
298
+ ),
264
299
  ]
265
300
 
266
- open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
301
+ mr_parser.fetch_mrs(label=SAPM_LABEL)
302
+ open_mrs = mr_parser.get_open_batcher_mrs()
267
303
  assert len(open_mrs) == 0
268
- vcs.close_app_interface_mr.assert_has_calls(expected_calls) # type: ignore[attr-defined]
304
+ vcs.close_app_interface_mr.assert_has_calls(expected_calls, any_order=True) # type: ignore[attr-defined]
269
305
  assert vcs.close_app_interface_mr.call_count == len(expected_calls) # type: ignore[attr-defined]
270
306
 
271
307
 
@@ -283,6 +319,7 @@ def test_remove_duplicates(
283
319
  {CHANNELS_REF}: some_channel
284
320
  {CONTENT_HASHES}: same_hash
285
321
  {IS_BATCHABLE}: True
322
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
286
323
  """,
287
324
  },
288
325
  {
@@ -294,6 +331,7 @@ def test_remove_duplicates(
294
331
  {CHANNELS_REF}: some_channel
295
332
  {CONTENT_HASHES}: same_hash
296
333
  {IS_BATCHABLE}: True
334
+ {MR_KIND_REF}: {MRKind.BATCHER.value}
297
335
  """,
298
336
  },
299
337
  ]
@@ -301,7 +339,8 @@ def test_remove_duplicates(
301
339
  mr_parser = MRParser(
302
340
  vcs=vcs,
303
341
  )
304
- open_mrs = mr_parser.retrieve_open_mrs(label=SAPM_LABEL)
342
+ mr_parser.fetch_mrs(label=SAPM_LABEL)
343
+ open_mrs = mr_parser.get_open_batcher_mrs()
305
344
  vcs.close_app_interface_mr.assert_has_calls([ # type: ignore[attr-defined]
306
345
  call(
307
346
  expected_mrs[1],
@@ -4,16 +4,16 @@ from unittest.mock import create_autospec
4
4
  import pytest
5
5
  from gitlab.v4.objects import ProjectMergeRequest
6
6
 
7
- from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
8
- OpenMergeRequest,
9
- )
10
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
7
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
11
8
  Addition,
9
+ Batcher,
12
10
  Deletion,
13
11
  Diff,
14
12
  Promotion,
15
13
  Reason,
16
- Reconciler,
14
+ )
15
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.open_merge_requests import (
16
+ OpenBatcherMergeRequest,
17
17
  )
18
18
 
19
19
 
@@ -69,28 +69,28 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
69
69
  (
70
70
  [],
71
71
  [
72
- OpenMergeRequest(
72
+ OpenBatcherMergeRequest(
73
73
  raw=create_autospec(spec=ProjectMergeRequest),
74
74
  channels={"chan1", "chan2"},
75
75
  content_hashes={"hash1", "hash2"},
76
76
  failed_mr_check=True,
77
77
  is_batchable=True,
78
78
  ),
79
- OpenMergeRequest(
79
+ OpenBatcherMergeRequest(
80
80
  raw=create_autospec(spec=ProjectMergeRequest),
81
81
  channels={"chan3"},
82
82
  content_hashes={"hash3"},
83
83
  failed_mr_check=False,
84
84
  is_batchable=True,
85
85
  ),
86
- OpenMergeRequest(
86
+ OpenBatcherMergeRequest(
87
87
  raw=create_autospec(spec=ProjectMergeRequest),
88
88
  channels={"chan4"},
89
89
  content_hashes={"hash4"},
90
90
  failed_mr_check=False,
91
91
  is_batchable=False,
92
92
  ),
93
- OpenMergeRequest(
93
+ OpenBatcherMergeRequest(
94
94
  raw=create_autospec(spec=ProjectMergeRequest),
95
95
  channels={"chan5"},
96
96
  content_hashes={"hash5"},
@@ -101,7 +101,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
101
101
  Diff(
102
102
  deletions=[
103
103
  Deletion(
104
- mr=OpenMergeRequest(
104
+ mr=OpenBatcherMergeRequest(
105
105
  raw=create_autospec(spec=ProjectMergeRequest),
106
106
  channels={"chan1", "chan2"},
107
107
  content_hashes={"hash1", "hash2"},
@@ -111,7 +111,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
111
111
  reason=Reason.MISSING_UNBATCHING,
112
112
  ),
113
113
  Deletion(
114
- mr=OpenMergeRequest(
114
+ mr=OpenBatcherMergeRequest(
115
115
  raw=create_autospec(spec=ProjectMergeRequest),
116
116
  channels={"chan3"},
117
117
  content_hashes={"hash3"},
@@ -121,7 +121,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
121
121
  reason=Reason.OUTDATED_CONTENT,
122
122
  ),
123
123
  Deletion(
124
- mr=OpenMergeRequest(
124
+ mr=OpenBatcherMergeRequest(
125
125
  raw=create_autospec(spec=ProjectMergeRequest),
126
126
  channels={"chan4"},
127
127
  content_hashes={"hash4"},
@@ -131,7 +131,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
131
131
  reason=Reason.OUTDATED_CONTENT,
132
132
  ),
133
133
  Deletion(
134
- mr=OpenMergeRequest(
134
+ mr=OpenBatcherMergeRequest(
135
135
  raw=create_autospec(spec=ProjectMergeRequest),
136
136
  channels={"chan5"},
137
137
  content_hashes={"hash5"},
@@ -161,7 +161,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
161
161
  ),
162
162
  ],
163
163
  [
164
- OpenMergeRequest(
164
+ OpenBatcherMergeRequest(
165
165
  raw=create_autospec(spec=ProjectMergeRequest),
166
166
  channels={"chan1", "chan2"},
167
167
  content_hashes={"hash1", "hash2"},
@@ -172,7 +172,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
172
172
  Diff(
173
173
  deletions=[
174
174
  Deletion(
175
- mr=OpenMergeRequest(
175
+ mr=OpenBatcherMergeRequest(
176
176
  raw=create_autospec(spec=ProjectMergeRequest),
177
177
  channels={"chan1", "chan2"},
178
178
  content_hashes={"hash1", "hash2"},
@@ -210,7 +210,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
210
210
  ),
211
211
  ],
212
212
  [
213
- OpenMergeRequest(
213
+ OpenBatcherMergeRequest(
214
214
  raw=create_autospec(spec=ProjectMergeRequest),
215
215
  channels={"chan1", "chan2"},
216
216
  content_hashes={"hash1", "hash2"},
@@ -243,7 +243,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
243
243
  ),
244
244
  ],
245
245
  [
246
- OpenMergeRequest(
246
+ OpenBatcherMergeRequest(
247
247
  raw=create_autospec(spec=ProjectMergeRequest),
248
248
  channels={"chan1", "chan2"},
249
249
  content_hashes={"hash1", "hash2"},
@@ -254,7 +254,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
254
254
  Diff(
255
255
  deletions=[
256
256
  Deletion(
257
- mr=OpenMergeRequest(
257
+ mr=OpenBatcherMergeRequest(
258
258
  raw=create_autospec(spec=ProjectMergeRequest),
259
259
  channels={"chan1", "chan2"},
260
260
  content_hashes={"hash1", "hash2"},
@@ -301,7 +301,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
301
301
  ),
302
302
  ],
303
303
  [
304
- OpenMergeRequest(
304
+ OpenBatcherMergeRequest(
305
305
  raw=create_autospec(spec=ProjectMergeRequest),
306
306
  channels={"chan1", "chan2"},
307
307
  content_hashes={"hash1", "hash2"},
@@ -312,7 +312,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
312
312
  Diff(
313
313
  deletions=[
314
314
  Deletion(
315
- mr=OpenMergeRequest(
315
+ mr=OpenBatcherMergeRequest(
316
316
  raw=create_autospec(spec=ProjectMergeRequest),
317
317
  channels={"chan1", "chan2"},
318
318
  content_hashes={"hash1", "hash2"},
@@ -380,7 +380,7 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
380
380
  ),
381
381
  ],
382
382
  [
383
- OpenMergeRequest(
383
+ OpenBatcherMergeRequest(
384
384
  raw=create_autospec(spec=ProjectMergeRequest),
385
385
  channels={"chan1"},
386
386
  content_hashes={"hash1"},
@@ -424,14 +424,14 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
424
424
  ),
425
425
  ],
426
426
  [
427
- OpenMergeRequest(
427
+ OpenBatcherMergeRequest(
428
428
  raw=create_autospec(spec=ProjectMergeRequest),
429
429
  channels={"chan1"},
430
430
  content_hashes={"hash1"},
431
431
  failed_mr_check=False,
432
432
  is_batchable=False,
433
433
  ),
434
- OpenMergeRequest(
434
+ OpenBatcherMergeRequest(
435
435
  raw=create_autospec(spec=ProjectMergeRequest),
436
436
  channels={"chan2"},
437
437
  content_hashes={"hash2"},
@@ -457,10 +457,10 @@ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
457
457
  )
458
458
  def test_reconcile(
459
459
  desired_promotions: list[Promotion],
460
- open_mrs: list[OpenMergeRequest],
460
+ open_mrs: list[OpenBatcherMergeRequest],
461
461
  expected_diff: Diff,
462
462
  ) -> None:
463
- reconciler = Reconciler()
463
+ reconciler = Batcher()
464
464
  diff = reconciler.reconcile(
465
465
  desired_promotions=desired_promotions, open_mrs=open_mrs, batch_limit=5
466
466
  )
@@ -1,15 +1,15 @@
1
1
  from unittest.mock import create_autospec
2
2
 
3
3
  from reconcile.saas_auto_promotions_manager.integration import SaasAutoPromotionsManager
4
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
5
+ Batcher,
6
+ )
4
7
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
5
8
  MergeRequestManagerV2,
6
9
  )
7
10
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
8
11
  MRParser,
9
12
  )
10
- from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
11
- Reconciler,
12
- )
13
13
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
14
14
  Renderer,
15
15
  )
@@ -33,7 +33,7 @@ def test_integration_test():
33
33
  vcs = create_autospec(spec=VCS)
34
34
  merge_request_manager_v2 = MergeRequestManagerV2(
35
35
  vcs=vcs,
36
- reconciler=create_autospec(spec=Reconciler),
36
+ reconciler=create_autospec(spec=Batcher),
37
37
  mr_parser=create_autospec(spec=MRParser),
38
38
  renderer=create_autospec(spec=Renderer),
39
39
  )
@@ -54,7 +54,7 @@ class Group(BaseModel):
54
54
  self.description == other.description
55
55
  and self.member_approval_type == other.member_approval_type
56
56
  and self.contact_list == other.contact_list
57
- and self.owners == other.owners
57
+ and set(self.owners) == set(other.owners)
58
58
  and self.display_name == other.display_name
59
59
  and self.notes == other.notes
60
60
  and set(self.members) == set(other.members)