qontract-reconcile 0.10.1rc696__py3-none-any.whl → 0.10.1rc702__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 (42) hide show
  1. {qontract_reconcile-0.10.1rc696.dist-info → qontract_reconcile-0.10.1rc702.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc696.dist-info → qontract_reconcile-0.10.1rc702.dist-info}/RECORD +42 -18
  3. reconcile/aws_account_manager/__init__.py +0 -0
  4. reconcile/aws_account_manager/integration.py +342 -0
  5. reconcile/aws_account_manager/merge_request_manager.py +111 -0
  6. reconcile/aws_account_manager/reconciler.py +353 -0
  7. reconcile/aws_account_manager/utils.py +38 -0
  8. reconcile/aws_saml_idp/integration.py +2 -0
  9. reconcile/aws_version_sync/integration.py +12 -11
  10. reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py +39 -112
  11. reconcile/cli.py +79 -0
  12. reconcile/gql_definitions/aws_account_manager/__init__.py +0 -0
  13. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +163 -0
  14. reconcile/gql_definitions/cost_report/__init__.py +0 -0
  15. reconcile/gql_definitions/cost_report/app_names.py +68 -0
  16. reconcile/gql_definitions/cost_report/settings.py +77 -0
  17. reconcile/gql_definitions/fragments/aws_account_managed.py +49 -0
  18. reconcile/queries.py +7 -1
  19. reconcile/templating/lib/merge_request_manager.py +8 -82
  20. reconcile/templating/renderer.py +2 -2
  21. reconcile/typed_queries/cost_report/__init__.py +0 -0
  22. reconcile/typed_queries/cost_report/app_names.py +22 -0
  23. reconcile/typed_queries/cost_report/settings.py +15 -0
  24. reconcile/utils/aws_api_typed/api.py +49 -6
  25. reconcile/utils/aws_api_typed/iam.py +22 -7
  26. reconcile/utils/aws_api_typed/organization.py +78 -30
  27. reconcile/utils/aws_api_typed/service_quotas.py +79 -0
  28. reconcile/utils/aws_api_typed/support.py +79 -0
  29. reconcile/utils/merge_request_manager/merge_request_manager.py +102 -0
  30. reconcile/utils/oauth2_backend_application_session.py +102 -0
  31. reconcile/utils/state.py +42 -38
  32. tools/cli_commands/cost_report/__init__.py +0 -0
  33. tools/cli_commands/cost_report/command.py +172 -0
  34. tools/cli_commands/cost_report/cost_management_api.py +57 -0
  35. tools/cli_commands/cost_report/model.py +29 -0
  36. tools/cli_commands/cost_report/response.py +48 -0
  37. tools/cli_commands/cost_report/view.py +333 -0
  38. tools/qontract_cli.py +10 -2
  39. tools/test/test_qontract_cli.py +20 -0
  40. {qontract_reconcile-0.10.1rc696.dist-info → qontract_reconcile-0.10.1rc702.dist-info}/WHEEL +0 -0
  41. {qontract_reconcile-0.10.1rc696.dist-info → qontract_reconcile-0.10.1rc702.dist-info}/entry_points.txt +0 -0
  42. {qontract_reconcile-0.10.1rc696.dist-info → qontract_reconcile-0.10.1rc702.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.1rc696
3
+ Version: 0.10.1rc702
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
@@ -9,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
9
9
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
10
10
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
11
11
  reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
12
- reconcile/cli.py,sha256=Xj_msd-518yR9r1hy39FI3Q9gBRZQsfM9HRA-Ep6CKY,93661
12
+ reconcile/cli.py,sha256=deAj6fNIYnrEKgOKv2UYZKAvuvEmYVUWj4nDSG6y99U,96070
13
13
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
14
14
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
15
15
  reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
@@ -92,7 +92,7 @@ reconcile/quay_mirror.py,sha256=9NzbNoxl-NdD8CwImcXNG5xTdHmUJxBfeVk5XHH41J8,1488
92
92
  reconcile/quay_mirror_org.py,sha256=Oq-t3kSkgfeSAOUDjLCDRBeEvOIEBacfX38qrX_s0oc,10801
93
93
  reconcile/quay_permissions.py,sha256=9KOutS1w4RFQqkvMSy54VtsKNx56-phzP6yI_rEW-B8,4244
94
94
  reconcile/quay_repos.py,sha256=cuEYG0HUe0ut5yvLdEwOF5-CmccpXQHRb_wDazvDrvQ,6895
95
- reconcile/queries.py,sha256=hgQizeUT5_a1ZI9i8wH-ajr7e956u5ynIp90zKMeRCA,50560
95
+ reconcile/queries.py,sha256=NFYbAkPkffZDU6rW1tn9r16jjLSmqWLPKBO6FkSbUWg,50829
96
96
  reconcile/query_validator.py,sha256=BAjGrU8_VhzTOv5k0-uz0hY9ziZyconv8VAhgre1Auc,1497
97
97
  reconcile/requests_sender.py,sha256=914iluuF4UVgG3VyxxtnHOu4yf6YKS2fIy6PViSsFTQ,3875
98
98
  reconcile/resource_scraper.py,sha256=vo1N9vLJCYWvXlTwFRIpEuWjx_39ZV9zxJlpoPq4g3U,2330
@@ -135,20 +135,25 @@ reconcile/aus/version_gates/handler.py,sha256=S_isQPYHbG4DERiUEvQBZ6ngiFX3uMmATA
135
135
  reconcile/aus/version_gates/ingress_gate_handler.py,sha256=ZCtyggBzzcb0prtdbMpJsVkj5leYN-vS7srM9vbq9xo,1096
136
136
  reconcile/aus/version_gates/ocp_gate_handler.py,sha256=RW1ppDaCZXVegV9AzzqYXxDUu_Z_7d43Z5h2Pk_piKc,716
137
137
  reconcile/aus/version_gates/sts_version_gate_handler.py,sha256=PhJ7yBh2q-rv9CJcfFhc0H11nyDyG7NAryNS3F74xdY,3697
138
+ reconcile/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
139
+ reconcile/aws_account_manager/integration.py,sha256=TLlhxnHXRCVz2GYJQei-dBdSpeLseEkoVUwHhgi41fk,13804
140
+ reconcile/aws_account_manager/merge_request_manager.py,sha256=zZct3NxWMBQupl4QfD7ULxnt4ipt_2FBoH_NusboIuw,3781
141
+ reconcile/aws_account_manager/reconciler.py,sha256=AqAA3TIEfuYzIogHSBgwYTebxbTy1D6JhcxdLiOfCsc,13588
142
+ reconcile/aws_account_manager/utils.py,sha256=K4rAjEMK-eQ_Sv4lOf6dPynQy97xZ4h-n6cJn5Z6zVw,1248
138
143
  reconcile/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
139
144
  reconcile/aws_ami_cleanup/integration.py,sha256=IW95cpMj2P5ffs-AxsR_TDQCJnYFBhLIfP2de7dz_8A,10109
140
145
  reconcile/aws_cloudwatch_log_retention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
146
  reconcile/aws_cloudwatch_log_retention/integration.py,sha256=0UcSZIrGvnGY4m9fj87oejIolIP_qTxtJInpmW9jrQ0,7772
142
147
  reconcile/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
- reconcile/aws_saml_idp/integration.py,sha256=uqec-EnxnfGOgQtg33S-Q1wTCv0sVBHNo02aT94hXrw,4807
148
+ reconcile/aws_saml_idp/integration.py,sha256=q0usjBp79aydlcD8kAq-5T2NKhZgEblGLvBBPHiJdKw,4956
144
149
  reconcile/aws_saml_roles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
145
150
  reconcile/aws_saml_roles/integration.py,sha256=kC4Rnbuy07TMvZO4rjUEcQkJev10M0Ro6r7YXcB7j_c,9530
146
151
  reconcile/aws_version_sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
- reconcile/aws_version_sync/integration.py,sha256=4XFCwlndYVwqajOl0oUaq7F7_uvqGKU3jDVqLhZoLus,17306
152
+ reconcile/aws_version_sync/integration.py,sha256=uI6k0nNS_jRrVaIcgm30Hj_M6GIJmexU2X-6Dxe0CZo,17271
148
153
  reconcile/aws_version_sync/utils.py,sha256=sVv-48PKi2VITlqqvmpbjnFDOPeGqfKzgkpIszlmjL0,1708
149
154
  reconcile/aws_version_sync/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
155
  reconcile/aws_version_sync/merge_request_manager/merge_request.py,sha256=2FbqLLdqxycWNvX1eNbwMjWSVBb7q0p-8t5Db0m7b4Q,4842
151
- reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py,sha256=NGk16KTblMPhsALYFcvB1agVs0BAka8ph9B2wuiuRb0,8236
156
+ reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py,sha256=3bRpw7DluiYw3daRg0yAyCSGYf39Ru0d8lUjoepDSpU,5525
152
157
  reconcile/change_owners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
158
  reconcile/change_owners/approver.py,sha256=GV8nwS-YJOJ8O-b9v3u60RSYECYH2EKAycjpoW6VmvU,2228
154
159
  reconcile/change_owners/bundle.py,sha256=dZ-GRCIgpSYwKzZD9rs64Ie09OptzDc8aR2X2msnt3Q,5363
@@ -185,6 +190,8 @@ reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py,sha256=zrZCHa
185
190
  reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py,sha256=uFx75cLe1a4xyArr6ekoAUbrzSRnhR7S2vaR3G5Fzbw,3299
186
191
  reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
192
  reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py,sha256=uVEEqU6YYmKsNTo6EWlFnoVmqha2rvBDx-wiD64VmG0,1679
193
+ reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
+ reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=c3RmQwbHa9UlfGwruufkA8PUfeiRJZ-lXEDInAREZqE,4405
188
195
  reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
196
  reconcile/gql_definitions/aws_ami_cleanup/asg_namespaces.py,sha256=OJmeTu7uirLGAysZ3IQTtRXqMyL8noi_QZxPuWYxxmI,3678
190
197
  reconcile/gql_definitions/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -230,6 +237,9 @@ reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BE
230
237
  reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
231
238
  reconcile/gql_definitions/common/state_aws_account.py,sha256=LAdpCG2-ykVpWBPO0Zu1WvG-hwKXyDC0fJQxJRpbqCk,2198
232
239
  reconcile/gql_definitions/common/users.py,sha256=uDiEDqa4QP89I2oFuKhCtVB61ZviIt7Y75fgrcCm7M4,1681
240
+ reconcile/gql_definitions/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
241
+ reconcile/gql_definitions/cost_report/app_names.py,sha256=fzqYXyiTSll359J1F1o7qapco0MSxgs3sr_Ssb2Kbns,1786
242
+ reconcile/gql_definitions/cost_report/settings.py,sha256=0nhBDJ5MZ1m7XkNDGrRLmsnUbzqZ4WRh_DDEEzKhcxU,2153
233
243
  reconcile/gql_definitions/dashdotdb_slo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
234
244
  reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py,sha256=zUa-CmpOwiymVmOV6KwDHH5mMl06p000320FcOas6hU,4315
235
245
  reconcile/gql_definitions/dynatrace_token_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -237,6 +247,7 @@ reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py
237
247
  reconcile/gql_definitions/fragments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
238
248
  reconcile/gql_definitions/fragments/aus_organization.py,sha256=ARI87YAbC0VjFri9eVGYrRPBc4s0kWsa25RR8FFoq7E,4433
239
249
  reconcile/gql_definitions/fragments/aws_account_common.py,sha256=d_FwpS_dY8o8DCLa3NERs93FVxQLiDUIPm5tGNac-iw,2320
250
+ reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=zXbux0Bb7QZ37fU4LLMKB4m0rT3Y8bD10C1TuOsH_ZQ,1470
240
251
  reconcile/gql_definitions/fragments/aws_account_sso.py,sha256=ITR3PLz4Iq1SiWAoYGWPDuHJnAmTyZ0QQqs2Zsi8pxA,979
241
252
  reconcile/gql_definitions/fragments/aws_infra_management_account.py,sha256=uAmALVRF2gBM3p_Dmez_ew6KVAtetamwOPkRIPZAlGc,1254
242
253
  reconcile/gql_definitions/fragments/aws_vpc.py,sha256=T2egTwi2Rb0IRBBmsyag8xKpu_m6GbIAy80fhZNZwk8,1434
@@ -400,10 +411,10 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
400
411
  reconcile/templates/rosa-classic-cluster-creation.sh.j2,sha256=0UHfYtXRVJqP07VJQx456cRI6EbZNBgamtP_8nb4WPY,2353
401
412
  reconcile/templates/rosa-hcp-cluster-creation.sh.j2,sha256=O7Bf3WQIJhsZoEqaYA0wRktUO4yXXCb4BQkuvvp-C80,2385
402
413
  reconcile/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
403
- reconcile/templating/renderer.py,sha256=Dbrap_sR6pxdoS35uYndtFucn4y7qda4SYDQyOd5Usw,8515
414
+ reconcile/templating/renderer.py,sha256=xXCzRuhkDOCPELRKzjkAARTgs4iOoNcdDN_hlKJxyjg,8516
404
415
  reconcile/templating/validator.py,sha256=QGH2VSk7sVBuojhk9quRAbj_XBykHN-KZ53DbEneUJs,4391
405
416
  reconcile/templating/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
- reconcile/templating/lib/merge_request_manager.py,sha256=tf4BJt-WEneuoE7-yDCj4e-AAa4XDOILW7_TpZ6q-dM,7750
417
+ reconcile/templating/lib/merge_request_manager.py,sha256=El3ufdVjHP4EW-LztX7zPiI9syJBJ6ETGRmiM9K4Nqw,5112
407
418
  reconcile/templating/lib/model.py,sha256=TYiH2xL63awd30U-fkZDLEzuxkLdUEgzJ913DEzpFoM,265
408
419
  reconcile/templating/lib/rendering.py,sha256=_BVQ2gqip8K1AgLYfaTWh8NKJFTW6VjUZ6rBI_GH30E,5061
409
420
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -546,6 +557,9 @@ reconcile/typed_queries/tekton_pipeline_providers.py,sha256=2mpHBdsNPQB94tw0H9ae
546
557
  reconcile/typed_queries/terraform_namespaces.py,sha256=71ARJ-GzkU9tBM0IfJTL3NF4349SJy-Mgs_DwAgUz_g,444
547
558
  reconcile/typed_queries/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
548
559
  reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py,sha256=X-N1WJGOL6OR9940P0_K4-YJzkL5Vg4favhYrBxXD9A,327
560
+ reconcile/typed_queries/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
561
+ reconcile/typed_queries/cost_report/app_names.py,sha256=HMEMIqAbMyVQfoQ5YXTXE4xDt7FaXBRz0QIHnsIZC1c,478
562
+ reconcile/typed_queries/cost_report/settings.py,sha256=xbTMMUQnbub2pav4B-ctzzRe7ijjTv2bqfqdtb9OnO0,589
549
563
  reconcile/typed_queries/terraform_tgw_attachments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
550
564
  reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py,sha256=T5HSeyBcGKP-LzDmIzs-WlBwOtSenYpm0Odw5--xdOg,410
551
565
  reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -592,6 +606,7 @@ reconcile/utils/lean_terraform_client.py,sha256=zReyNPJbr2uOdrdh8Qfe-OZQBoRwxb5Z
592
606
  reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
593
607
  reconcile/utils/metrics.py,sha256=7nXdctmZ0UtGMHPpS3V55sfH4xpMPqdYaJ3JKAUc_sM,18474
594
608
  reconcile/utils/models.py,sha256=It_Q1WNIvw_EDCsiSWzIgpSPr_X9jMgbJI-DR3N23xY,4677
609
+ reconcile/utils/oauth2_backend_application_session.py,sha256=kWUX2LTwKniD01-0a7x-kV9ud3Q30DpLgh1xDbUhLSI,3298
595
610
  reconcile/utils/oc.py,sha256=ILAlP-AZMtWeyAepLoMnYbDJfyyMs-Z0fOEo9JXQfkE,65490
596
611
  reconcile/utils/oc_connection_parameters.py,sha256=85slrnDigYwYmzhyceVkMElWzFArp4ge1d-fHXVqh0w,9729
597
612
  reconcile/utils/oc_filters.py,sha256=R2Lf3fo0jQCeE62Ygeo_KN24XbAosq0QbjimYG6qHI4,1402
@@ -616,7 +631,7 @@ reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,8
616
631
  reconcile/utils/slack_api.py,sha256=OPmzU6L9rJx2XXDlZkMlxLjOWu17yC-fVCoUItzQrXw,16295
617
632
  reconcile/utils/smtp_client.py,sha256=gJNbBQJpAt5PX4t_TaeNHsXM8vt50bFgndml6yK2b5o,2800
618
633
  reconcile/utils/sqs_gateway.py,sha256=gFl9DM4DmGnptuxTOe4lS3YTyE80eSAvK42ljS8h4dA,2287
619
- reconcile/utils/state.py,sha256=zjsprjbOb0WddzmAvh8ACqAt0fcayrX2YPfz7qceRWw,16090
634
+ reconcile/utils/state.py,sha256=FK8NLT1xyumuXpYRm0Nk6pWpOE_U6-NovGn6zKCw8vw,16298
620
635
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
621
636
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
622
637
  reconcile/utils/terraform_client.py,sha256=mZEKpu6nbfiQd60wRkc8-5sljBTUTOgaAKnF89itMzU,32085
@@ -632,10 +647,12 @@ reconcile/utils/acs/base.py,sha256=Qih-xZ3RBJZEE291iHHlv7lUY6ShcAvSj1PA3_aTTnM,2
632
647
  reconcile/utils/acs/policies.py,sha256=_jAz6cv8KRYtDsXjGoJgNbD8_9PUa5LSwwVlpK4A_cQ,5505
633
648
  reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
634
649
  reconcile/utils/aws_api_typed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
635
- reconcile/utils/aws_api_typed/api.py,sha256=ICBrpoZ1Y8ShMaNOQmzPzChmw-Ffdg0tCsu97fVwZ-w,6374
636
- reconcile/utils/aws_api_typed/iam.py,sha256=wH82lA2kUgEKR5McmyU5gB8ASfemT5xAk3Bb9cairVo,1508
637
- reconcile/utils/aws_api_typed/organization.py,sha256=dJ7J02BNHu7UDyFa9083b92vSamIX8DtHIoF8VwEijY,3675
650
+ reconcile/utils/aws_api_typed/api.py,sha256=gqfZISSQLp6tHEAwEsroLWwyU4ZdbwHi9p0rNBQyLuI,7901
651
+ reconcile/utils/aws_api_typed/iam.py,sha256=ka46H2-SzTCgy6EJYapKTzyZK9vR1bkfD0wF8bDdy1Q,2201
652
+ reconcile/utils/aws_api_typed/organization.py,sha256=oXftcLVuSs9qej6efdssl38FvjeZaQC5R2Wj3NzxX4U,5529
653
+ reconcile/utils/aws_api_typed/service_quotas.py,sha256=OU1D8LCmMw1IT87nt45LqXhguzcWwC8AaBdDTI7tz98,3018
638
654
  reconcile/utils/aws_api_typed/sts.py,sha256=5Sauncj9Fif3YDLkJYkBZrtOX0v0bGAqOmY0A5Bh9yA,1237
655
+ reconcile/utils/aws_api_typed/support.py,sha256=PH3UW96Ne4_8I1J-_Vqj-DsK73gYGeVdOH13eD1783c,2447
639
656
  reconcile/utils/cloud_resource_best_practice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
640
657
  reconcile/utils/cloud_resource_best_practice/aws_rds.py,sha256=EvE6XKLsrZ531MJptKqPht2lOETrOjySTHXk6CzMgo0,2279
641
658
  reconcile/utils/clusterhealth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -659,6 +676,7 @@ reconcile/utils/membershipsources/app_interface_resolver.py,sha256=IlDiRtJZ0AfAG
659
676
  reconcile/utils/membershipsources/models.py,sha256=IFu6KHFe-HUTJPiAO3fEw7i22yv4_ytgBW-h_wrO6V4,2015
660
677
  reconcile/utils/membershipsources/resolver.py,sha256=meERrCZqeVJZR2lHdZEznaZDsCsUb194UEMOjbE4aYA,3168
661
678
  reconcile/utils/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
679
+ reconcile/utils/merge_request_manager/merge_request_manager.py,sha256=6i2uwSJoeYiZSRwcnBG5ICQXyIC8umvQ_TzubR7_jvA,3374
662
680
  reconcile/utils/merge_request_manager/parser.py,sha256=5pGoz8Q6EuYXlUc1z-D0FahdRP2YLO8CpACoa9HcgtQ,2098
663
681
  reconcile/utils/mr/__init__.py,sha256=JQS8xmLZdG4TgjAFZp2ltuV9c1sDFHIHJmpn1bkY9rM,2386
664
682
  reconcile/utils/mr/app_interface_reporter.py,sha256=6Kpg93V9FvcOke9Jimkva359MQ-ZyBIkUpf8QIA6-to,1793
@@ -717,20 +735,26 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
717
735
  tools/app_interface_reporter.py,sha256=upA-J-n-HXHKVDINRuMR7vTt-iJvQORKUVi9D3leQto,17738
718
736
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
719
737
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
720
- tools/qontract_cli.py,sha256=fxXsgjmLBzC1zDkmNnB5jfOnwL-kGci5xC_4BsoIe3M,111648
738
+ tools/qontract_cli.py,sha256=AdOUq_74y-s1nDbF6Yh0z1sTQ1r8yS3nPG9yP-slWfE,111846
721
739
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
722
740
  tools/template_validation.py,sha256=-U-lTGeLaci8yWPEblCJeev2DOlY1jM9QOOh-O1zts8,3376
723
741
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
724
742
  tools/cli_commands/gpg_encrypt.py,sha256=w8hl4jIEWk5wKbEFN6fVEOwUJGmdlvOqYodW3XSN7mU,4978
743
+ tools/cli_commands/cost_report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
744
+ tools/cli_commands/cost_report/command.py,sha256=ecT4SjCwhoWNFF9Xb1spqYzI7QMwX9EFWz6XFSW4bas,6317
745
+ tools/cli_commands/cost_report/cost_management_api.py,sha256=IuPbrtNpgt1wvzHAPQjGPdCobXIkvkusqP8uXv3G204,1743
746
+ tools/cli_commands/cost_report/model.py,sha256=blNk52T6KIDy-7w4V6qvZhGFWplxgDfZ5jRZimPDYJo,593
747
+ tools/cli_commands/cost_report/response.py,sha256=lOmIohSuwJBxoSAC9LCwihBsyWgutHCIW-nvd6_HX5Y,867
748
+ tools/cli_commands/cost_report/view.py,sha256=LtM5AVv1pZ9yT1C_IcTrHHyXlQkRWeS3nnDf5XQVoVQ,8554
725
749
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
726
750
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
727
751
  tools/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
728
752
  tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvftCWEEf-g1mfXOtgCog-g,1271
729
- tools/test/test_qontract_cli.py,sha256=OvalpVRfY4pNmpMaWHHYqBjV68b1eGQjX8SCyTAXb1w,3501
753
+ tools/test/test_qontract_cli.py,sha256=UEwAW7PA_GIrbqzaLxpkCxbuVjEFLNvnVG-6VyoCGIc,4147
730
754
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
731
755
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
732
- qontract_reconcile-0.10.1rc696.dist-info/METADATA,sha256=Ze93vzm8KdoglmXqBmbo4n-_lPtbMFpnX5J8ng2LFbQ,2382
733
- qontract_reconcile-0.10.1rc696.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
734
- qontract_reconcile-0.10.1rc696.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
735
- qontract_reconcile-0.10.1rc696.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
736
- qontract_reconcile-0.10.1rc696.dist-info/RECORD,,
756
+ qontract_reconcile-0.10.1rc702.dist-info/METADATA,sha256=Sk3NS11248E7ukRziTkbkvYPypcX1zWYqHoDJBXKxB4,2382
757
+ qontract_reconcile-0.10.1rc702.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
758
+ qontract_reconcile-0.10.1rc702.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
759
+ qontract_reconcile-0.10.1rc702.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
760
+ qontract_reconcile-0.10.1rc702.dist-info/RECORD,,
File without changes
@@ -0,0 +1,342 @@
1
+ from collections.abc import Callable, Iterable
2
+ from datetime import datetime, timezone
3
+ from typing import Any
4
+
5
+ import jinja2
6
+
7
+ from reconcile.aws_account_manager.merge_request_manager import MergeRequestManager
8
+ from reconcile.aws_account_manager.reconciler import AWSReconciler
9
+ from reconcile.aws_account_manager.utils import validate
10
+ from reconcile.gql_definitions.aws_account_manager.aws_accounts import (
11
+ AWSAccountManaged,
12
+ AWSAccountRequestV1,
13
+ AWSAccountV1,
14
+ )
15
+ from reconcile.gql_definitions.aws_account_manager.aws_accounts import (
16
+ query as aws_accounts_query,
17
+ )
18
+ from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
19
+ from reconcile.typed_queries.github_orgs import get_github_orgs
20
+ from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
21
+ from reconcile.utils import gql
22
+ from reconcile.utils.aws_api_typed.api import AWSApi, AWSStaticCredentials
23
+ from reconcile.utils.aws_api_typed.iam import AWSAccessKey
24
+ from reconcile.utils.defer import defer
25
+ from reconcile.utils.disabled_integrations import integration_is_enabled
26
+ from reconcile.utils.runtime.integration import (
27
+ PydanticRunParams,
28
+ QontractReconcileIntegration,
29
+ )
30
+ from reconcile.utils.semver_helper import make_semver
31
+ from reconcile.utils.state import init_state
32
+ from reconcile.utils.unleash import get_feature_toggle_state
33
+ from reconcile.utils.vcs import VCS
34
+
35
+ QONTRACT_INTEGRATION = "aws-account-manager"
36
+ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
37
+
38
+
39
+ class AwsAccountMgmtIntegrationParams(PydanticRunParams):
40
+ account_name: str | None
41
+ flavor: str
42
+ organization_account_role: str = "OrganizationAccountAccessRole"
43
+ default_tags: dict[str, str] = {}
44
+ initial_user_name: str = "terraform"
45
+ initial_user_policy_arn: str = "arn:aws:iam::aws:policy/AdministratorAccess"
46
+ initial_user_secret_vault_path: str = (
47
+ "app-sre/creds/terraform/{account_name}/config"
48
+ )
49
+ account_tmpl_resource: str = "/aws-account-manager/account-tmpl.yml"
50
+ template_collection_root_path: str = "data/templating/collections/aws-account"
51
+
52
+
53
+ class AwsAccountMgmtIntegration(
54
+ QontractReconcileIntegration[AwsAccountMgmtIntegrationParams]
55
+ ):
56
+ """Create and manage AWS accounts."""
57
+
58
+ @property
59
+ def name(self) -> str:
60
+ return QONTRACT_INTEGRATION
61
+
62
+ def get_early_exit_desired_state(
63
+ self, query_func: Callable | None = None
64
+ ) -> dict[str, Any]:
65
+ """Return the desired state for early exit."""
66
+ if not query_func:
67
+ query_func = gql.get_api().query
68
+ payer_accounts, non_organization_accounts = self.get_aws_accounts(
69
+ query_func, account_name=self.params.account_name
70
+ )
71
+ return {
72
+ "payer_accounts": [account.dict() for account in payer_accounts],
73
+ "non_organization_accounts": [
74
+ account.dict() for account in non_organization_accounts
75
+ ],
76
+ }
77
+
78
+ @staticmethod
79
+ def render_account_tmpl_file(
80
+ template: str, account_request: AWSAccountRequestV1, uid: str, settings: dict
81
+ ) -> str:
82
+ for k, v in settings.items():
83
+ if not isinstance(v, str):
84
+ continue
85
+ # render string templates with account name
86
+ settings[k] = v.format(account_name=account_request.name)
87
+ tmpl = jinja2.Template(
88
+ template,
89
+ undefined=jinja2.StrictUndefined,
90
+ trim_blocks=True,
91
+ lstrip_blocks=True,
92
+ keep_trailing_newline=True,
93
+ ).render({
94
+ "accountRequest": account_request.dict(by_alias=True),
95
+ "uid": uid,
96
+ "settings": settings,
97
+ "timestamp": int(datetime.now(tz=timezone.utc).timestamp()),
98
+ })
99
+ return tmpl
100
+
101
+ def get_aws_accounts(
102
+ self, query_func: Callable, account_name: str | None = None
103
+ ) -> tuple[list[AWSAccountV1], list[AWSAccountV1]]:
104
+ """Get all AWS payer and non-organization accounts."""
105
+ data = aws_accounts_query(query_func)
106
+
107
+ all_aws_accounts = [
108
+ account
109
+ for account in data.accounts or []
110
+ if integration_is_enabled(self.name, account)
111
+ and (not account_name or account.name == account_name)
112
+ ]
113
+ for account in all_aws_accounts:
114
+ validate(account)
115
+
116
+ payer_accounts = [
117
+ account
118
+ for account in all_aws_accounts
119
+ if account.organization_accounts or account.account_requests
120
+ ]
121
+ all_organization_account_names = {
122
+ org_account.name
123
+ for payer_account in payer_accounts
124
+ for org_account in payer_account.organization_accounts or []
125
+ }
126
+
127
+ non_organization_accounts = [
128
+ account
129
+ for account in all_aws_accounts
130
+ if account.name not in all_organization_account_names
131
+ ]
132
+ return payer_accounts, non_organization_accounts
133
+
134
+ def save_access_key(self, account: str, access_key: AWSAccessKey) -> None:
135
+ """Write the AWS secret to Vault."""
136
+ self.secret_reader.vault_client.write( # type: ignore[attr-defined] # mypy doesn't recognize the VaultClient.__new__ method
137
+ secret={
138
+ "data": {
139
+ "aws_access_key_id": access_key.access_key_id,
140
+ "aws_secret_access_key": access_key.secret_access_key,
141
+ },
142
+ "path": self.params.initial_user_secret_vault_path.format(
143
+ account_name=account
144
+ ).strip("/"),
145
+ },
146
+ decode_base64=False,
147
+ )
148
+
149
+ def create_accounts(
150
+ self,
151
+ aws_api: AWSApi,
152
+ reconciler: AWSReconciler,
153
+ merge_request_manager: MergeRequestManager,
154
+ account_template: str,
155
+ account_requests: Iterable[AWSAccountRequestV1],
156
+ ) -> None:
157
+ """Create new AWS accounts."""
158
+ for account_request in account_requests:
159
+ if not (
160
+ uid := reconciler.create_organization_account(
161
+ aws_api=aws_api,
162
+ name=account_request.name,
163
+ email=account_request.account_owner.email,
164
+ )
165
+ ):
166
+ continue
167
+
168
+ with aws_api.assume_role(
169
+ account_id=uid, role=self.params.organization_account_role
170
+ ) as account_role_api:
171
+ if access_key := reconciler.create_iam_user(
172
+ aws_api=account_role_api,
173
+ name=account_request.name,
174
+ user_name=self.params.initial_user_name,
175
+ user_policy_arn=self.params.initial_user_policy_arn,
176
+ ):
177
+ self.save_access_key(account_request.name, access_key)
178
+
179
+ merge_request_manager.create_account_file(
180
+ title=f"{account_request.name}: AWS account template collection file",
181
+ account_tmpl_file_path=f"{self.params.template_collection_root_path}/{account_request.name}.yml",
182
+ account_tmpl_file_content=self.render_account_tmpl_file(
183
+ template=account_template,
184
+ account_request=account_request,
185
+ uid=uid,
186
+ settings=self.params.dict(),
187
+ ),
188
+ account_request_file_path=f"data/{account_request.path.strip('/')}",
189
+ )
190
+
191
+ def reconcile_organization_accounts(
192
+ self,
193
+ aws_api: AWSApi,
194
+ reconciler: AWSReconciler,
195
+ organization_accounts: Iterable[AWSAccountManaged],
196
+ ) -> None:
197
+ """Reconcile organization accounts."""
198
+ for account in organization_accounts:
199
+ assert account.organization # mypy
200
+ reconciler.reconcile_organization_account(
201
+ aws_api=aws_api,
202
+ name=account.name,
203
+ uid=account.uid,
204
+ ou=account.organization.ou,
205
+ tags=self.params.default_tags
206
+ | account.organization.tags
207
+ | {"app-interface-name": account.name},
208
+ enterprise_support=account.premium_support,
209
+ )
210
+
211
+ with aws_api.assume_role(
212
+ account_id=account.uid, role=self.params.organization_account_role
213
+ ) as account_role_api:
214
+ self.reconcile_account(account_role_api, reconciler, account)
215
+
216
+ def reconcile_account(
217
+ self,
218
+ aws_api: AWSApi,
219
+ reconciler: AWSReconciler,
220
+ account: AWSAccountManaged,
221
+ create_initial_user: bool = True,
222
+ ) -> None:
223
+ """Reconcile an AWS account."""
224
+ reconciler.reconcile_account(
225
+ aws_api=aws_api,
226
+ name=account.name,
227
+ alias=account.alias,
228
+ quotas=[q for ql in account.quota_limits or [] for q in ql.quotas],
229
+ )
230
+
231
+ def reconcile_payer_accounts(
232
+ self,
233
+ reconciler: AWSReconciler,
234
+ merge_request_manager: MergeRequestManager,
235
+ default_state_path: str,
236
+ account_template: str,
237
+ payer_accounts: Iterable[AWSAccountV1],
238
+ ) -> None:
239
+ """Reconcile all payer accounts including account creation."""
240
+ # reconcile accounts within payer accounts, aka organization accounts
241
+ for payer_account in payer_accounts:
242
+ # having a state per flavor and payer account makes it easier in a shared environment
243
+ reconciler.state.state_path = default_state_path
244
+ reconciler.state.state_path += f"/{payer_account.name}"
245
+ aws_account_manager_role = (
246
+ payer_account.automation_role.aws_account_manager
247
+ if payer_account.automation_role
248
+ else None
249
+ )
250
+ if not aws_account_manager_role:
251
+ raise ValueError(
252
+ f"awsAccountManager role is not defined for account {payer_account.name}"
253
+ )
254
+
255
+ secret = self.secret_reader.read_all_secret(payer_account.automation_token)
256
+ with AWSApi(
257
+ AWSStaticCredentials(
258
+ access_key_id=secret["aws_access_key_id"],
259
+ secret_access_key=secret["aws_secret_access_key"],
260
+ region=payer_account.resources_default_region,
261
+ )
262
+ ) as payer_account_aws_api:
263
+ with payer_account_aws_api.assume_role(
264
+ account_id=payer_account.uid,
265
+ role=aws_account_manager_role,
266
+ ) as acct_manager_role_aws_api:
267
+ self.create_accounts(
268
+ acct_manager_role_aws_api,
269
+ reconciler,
270
+ merge_request_manager,
271
+ account_template,
272
+ payer_account.account_requests or [],
273
+ )
274
+ self.reconcile_organization_accounts(
275
+ acct_manager_role_aws_api,
276
+ reconciler,
277
+ payer_account.organization_accounts or [],
278
+ )
279
+
280
+ def reconcile_non_organization_accounts(
281
+ self,
282
+ reconciler: AWSReconciler,
283
+ default_state_path: str,
284
+ non_organization_accounts: Iterable[AWSAccountV1],
285
+ ) -> None:
286
+ """Reconcile accounts not part of an organization via a payer account (e.g. payer accounts themselves)"""
287
+ for account in non_organization_accounts:
288
+ reconciler.state.state_path = default_state_path
289
+ reconciler.state.state_path += f"/{account.name}"
290
+ secret = self.secret_reader.read_all_secret(account.automation_token)
291
+ with AWSApi(
292
+ AWSStaticCredentials(
293
+ access_key_id=secret["aws_access_key_id"],
294
+ secret_access_key=secret["aws_secret_access_key"],
295
+ region=account.resources_default_region,
296
+ )
297
+ ) as account_aws_api:
298
+ self.reconcile_account(account_aws_api, reconciler, account)
299
+
300
+ @defer
301
+ def run(self, dry_run: bool, defer: Callable | None = None) -> None:
302
+ """Run the integration."""
303
+ gql_api = gql.get_api()
304
+ payer_accounts, non_organization_accounts = self.get_aws_accounts(
305
+ gql_api.query, account_name=self.params.account_name
306
+ )
307
+ state = init_state(self.name, self.secret_reader)
308
+ default_state_path = f"state/{self.name}/{self.params.flavor}"
309
+ reconciler = AWSReconciler(state, dry_run)
310
+ vcs = VCS(
311
+ secret_reader=self.secret_reader,
312
+ github_orgs=get_github_orgs(),
313
+ gitlab_instances=get_gitlab_instances(),
314
+ app_interface_repo_url=get_app_interface_repo_url(),
315
+ dry_run=dry_run,
316
+ allow_deleting_mrs=False,
317
+ allow_opening_mrs=True,
318
+ )
319
+ if defer:
320
+ defer(vcs.cleanup)
321
+ merge_request_manager = MergeRequestManager(
322
+ vcs=vcs,
323
+ auto_merge_enabled=get_feature_toggle_state(
324
+ integration_name=f"{self.name}-allow-auto-merge-mrs", default=False
325
+ ),
326
+ )
327
+ merge_request_manager.fetch_open_merge_requests()
328
+ account_template = gql_api.get_resource(path=self.params.account_tmpl_resource)[
329
+ "content"
330
+ ]
331
+ self.reconcile_payer_accounts(
332
+ reconciler=reconciler,
333
+ merge_request_manager=merge_request_manager,
334
+ default_state_path=default_state_path,
335
+ account_template=account_template,
336
+ payer_accounts=payer_accounts,
337
+ )
338
+ self.reconcile_non_organization_accounts(
339
+ reconciler=reconciler,
340
+ default_state_path=default_state_path,
341
+ non_organization_accounts=non_organization_accounts,
342
+ )
@@ -0,0 +1,111 @@
1
+ import logging
2
+
3
+ from gitlab.exceptions import GitlabGetError
4
+ from gitlab.v4.objects import ProjectMergeRequest
5
+
6
+ from reconcile.utils.gitlab_api import GitLabApi
7
+ from reconcile.utils.mr.base import MergeRequestBase
8
+ from reconcile.utils.mr.labels import AUTO_MERGE
9
+ from reconcile.utils.vcs import VCS
10
+
11
+ AWS_MGR = "aws-account-manager"
12
+
13
+
14
+ class AwsAccountMR(MergeRequestBase):
15
+ name = "AwsAccount"
16
+
17
+ def __init__(
18
+ self,
19
+ title: str,
20
+ description: str,
21
+ account_tmpl_file_path: str,
22
+ account_tmpl_file_content: str,
23
+ account_request_file_path: str,
24
+ labels: list[str],
25
+ ):
26
+ super().__init__()
27
+ self._title = title
28
+ self._description = description
29
+ self._account_tmpl_file_path = account_tmpl_file_path.lstrip("/")
30
+ self._account_tmpl_file_content = account_tmpl_file_content
31
+ self._account_request_file_path = account_request_file_path.lstrip("/")
32
+ self.labels = labels
33
+
34
+ @property
35
+ def title(self) -> str:
36
+ return self._title
37
+
38
+ @property
39
+ def description(self) -> str:
40
+ return self._description
41
+
42
+ def process(self, gitlab_cli: GitLabApi) -> None:
43
+ gitlab_cli.create_file(
44
+ branch_name=self.branch,
45
+ file_path=self._account_tmpl_file_path,
46
+ commit_message="add account template file",
47
+ content=self._account_tmpl_file_content,
48
+ )
49
+ gitlab_cli.delete_file(
50
+ branch_name=self.branch,
51
+ file_path=self._account_request_file_path,
52
+ commit_message="delete account request file",
53
+ )
54
+
55
+
56
+ class MergeRequestManager:
57
+ """Manager for AWS account merge requests."""
58
+
59
+ def __init__(self, vcs: VCS, auto_merge_enabled: bool):
60
+ self._open_mrs: list[ProjectMergeRequest] = []
61
+ self._vcs = vcs
62
+ self._auto_merge_enabled = auto_merge_enabled
63
+
64
+ def _merge_request_already_exists(self, aws_acccount_file_path: str) -> bool:
65
+ return any(
66
+ aws_acccount_file_path == diff["new_path"]
67
+ for mr in self._open_mrs
68
+ for diff in mr.changes()["changes"]
69
+ )
70
+
71
+ def fetch_open_merge_requests(self) -> None:
72
+ all_open_mrs = self._vcs.get_open_app_interface_merge_requests()
73
+ self._open_mrs = [mr for mr in all_open_mrs if AWS_MGR in mr.labels]
74
+
75
+ def create_account_file(
76
+ self,
77
+ title: str,
78
+ account_tmpl_file_path: str,
79
+ account_tmpl_file_content: str,
80
+ account_request_file_path: str,
81
+ ) -> None:
82
+ """Open new MR (if not already present) for an AWS account and remove the account request file."""
83
+ if self._merge_request_already_exists(account_tmpl_file_path):
84
+ return None
85
+
86
+ try:
87
+ self._vcs.get_file_content_from_app_interface_master(
88
+ file_path=account_tmpl_file_path
89
+ )
90
+ # File already exists
91
+ raise FileExistsError(
92
+ f"File {account_tmpl_file_path} already exists in the repository"
93
+ )
94
+ except GitlabGetError as e:
95
+ if e.response_code != 404:
96
+ raise e
97
+
98
+ logging.info("Open MR for %s", account_tmpl_file_path)
99
+ mr_labels = [AWS_MGR]
100
+ if self._auto_merge_enabled:
101
+ mr_labels.append(AUTO_MERGE)
102
+ self._vcs.open_app_interface_merge_request(
103
+ mr=AwsAccountMR(
104
+ title=title,
105
+ description=f"New AWS account template collection file {account_tmpl_file_path}",
106
+ account_tmpl_file_path=account_tmpl_file_path,
107
+ account_tmpl_file_content=account_tmpl_file_content,
108
+ account_request_file_path=account_request_file_path,
109
+ labels=mr_labels,
110
+ )
111
+ )