qontract-reconcile 0.10.1rc1061__py3-none-any.whl → 0.10.1rc1063__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc1061
3
+ Version: 0.10.1rc1063
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
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=q96mwr2KeEQ5bpNniGlgIMZTxiuLSodcYfX-t
10
10
  reconcile/aws_support_cases_sos.py,sha256=hl_9L53yQYRQxKs3IWrd69Cc60XK067g_bJRM9B0udo,2975
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
12
12
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
13
- reconcile/cli.py,sha256=JCKHmEpzb1MnjtyBlyRPo27J2z03J1rrQ680Nlu7_m8,106890
13
+ reconcile/cli.py,sha256=PHoUlP86m-TdTpB3hyoHbOEDWR0_ZO-GSBQEGTmbTmc,107514
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=rLh16BOlBOxTmJ8Si3wWyyEpmMlhh4Znx1Gc36qsmOc,4865
15
15
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
16
16
  reconcile/dashdotdb_base.py,sha256=l34QDu1G96_Ctnh7ZXdxXgSeCE93GQMdLAkWxmN6vDA,4775
@@ -114,7 +114,7 @@ reconcile/terraform_cloudflare_resources.py,sha256=41Mj1WkuS75slCDpmhG2GGf1nh3Bw
114
114
  reconcile/terraform_cloudflare_users.py,sha256=iyTG5sj20Jg4J4qWJ144KVptfIHGOSfH8wQKxu0imq0,13942
115
115
  reconcile/terraform_repo.py,sha256=TKqlodhQGoAtQ6nDm04TNlpx4wpgJ_n4atoUK5Rfd7o,16444
116
116
  reconcile/terraform_resources.py,sha256=-sgMMHDtNvnQyNR05-MKebI_pSiyxSWAg8LmeA2_Ntk,19326
117
- reconcile/terraform_tgw_attachments.py,sha256=EucuF4p3RWKTS4GTPd8oZmR79GpIW_grQl2PAeeNQeI,18665
117
+ reconcile/terraform_tgw_attachments.py,sha256=09svJG9pAiwWp4aY0xRoQRV90T4ZNwHG3r8flI-ZS_s,18810
118
118
  reconcile/terraform_users.py,sha256=HqSm3ev3b8dZ9J6F_phDZB-FQsnlsdeKp9RPoY1cU94,10188
119
119
  reconcile/terraform_vpc_peerings.py,sha256=VLSfuO7FvHN5McopRiKoKJDHCmIhYtlJEHv_hxV5kcM,27669
120
120
  reconcile/vault_replication.py,sha256=isfmNaqxl4AC90n8sVJffUt685sPBfhNSvjks6DoQXg,17339
@@ -161,9 +161,10 @@ reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py,sha256
161
161
  reconcile/change_owners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
162
  reconcile/change_owners/approver.py,sha256=Z3_11vnK2WNOxjEEXVDh0224-_-qbt9d6mBeVE-7fsc,2259
163
163
  reconcile/change_owners/bundle.py,sha256=ZIlXRo6Z2raeWSCUqYsexBdol-q-r9kWJs5O_YPaEYk,5273
164
+ reconcile/change_owners/change_log_tracking.py,sha256=zFpean5UB3-u5VQLJHzik8GmhBwYoNorR-l90-D1Pis,4160
164
165
  reconcile/change_owners/change_owners.py,sha256=0HRJhDm0oW3uYJFgzynqA1gA0lbhalhSkmWOiQmr-NM,17062
165
- reconcile/change_owners/change_types.py,sha256=PMDEWOAbRKjES3WDPGe_8bcrZgK3efLdVF22WvkUL3A,31938
166
- reconcile/change_owners/changes.py,sha256=CH38-hyOfbH6xFYWidw_LAniyPisKq9nGRQhUaT93do,17180
166
+ reconcile/change_owners/change_types.py,sha256=TjVtvmkU0s8w2NA6qvWQccB6PwlCrChFySlsHLYZjpE,32027
167
+ reconcile/change_owners/changes.py,sha256=pa3cNAL-Xawh700ARJJQjY0p09NA1J2329RcE0F0MHM,17224
167
168
  reconcile/change_owners/decision.py,sha256=iUJcIc_N_RqXIAY8D10RZqPMC2OinsHTMcqI6f6uylE,7606
168
169
  reconcile/change_owners/diff.py,sha256=0vyu29xCL24ZhUa7hqBni0NaxoCYRXLwvA-h8V23YQ4,9009
169
170
  reconcile/change_owners/implicit_ownership.py,sha256=6BehZvx4IjrphmOt_LLLk9_02Fl5BY5jd00Wuz_PBZk,4234
@@ -233,7 +234,7 @@ reconcile/gql_definitions/aws_version_sync/clusters.py,sha256=2TOJOFxpTkZ2HKuqAG
233
234
  reconcile/gql_definitions/aws_version_sync/namespaces.py,sha256=eBLyXlSjWdmEE-jY9M2Ocgk7JGi2OsWisTkjHLfgU_A,4311
234
235
  reconcile/gql_definitions/change_owners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
235
236
  reconcile/gql_definitions/change_owners/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
236
- reconcile/gql_definitions/change_owners/queries/change_types.py,sha256=9S2YRNnSAvutjzuubZIQQe35yd8V2rGKvWSUI6yl11Q,5017
237
+ reconcile/gql_definitions/change_owners/queries/change_types.py,sha256=SjpKbLWmLh8iwPwfulbjHpH-M1hqBG1TOu_pZazox0Q,5084
237
238
  reconcile/gql_definitions/change_owners/queries/self_service_roles.py,sha256=BcTQvnefPiShG90ajU_l2V6HUYSEXgdAzgiwY89vQew,4790
238
239
  reconcile/gql_definitions/cluster_auth_rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
239
240
  reconcile/gql_definitions/cluster_auth_rhidp/clusters.py,sha256=Pp9P3Q30Be3szcVqOEOtPfYUNiGTq1xc5Juz-ApMMw0,3283
@@ -255,7 +256,7 @@ reconcile/gql_definitions/common/aws_vpcs.py,sha256=Dss9dQ3xagnz3Ltg1e9mtG2PAmQG
255
256
  reconcile/gql_definitions/common/clusters.py,sha256=Dr5AsSsTuqjAxkI9fU0fdiaP6u5qkmRpkkCcYDnU584,21868
256
257
  reconcile/gql_definitions/common/clusters_minimal.py,sha256=JYrJV_aStmryiiGKyiXhj47qpF_8KilCqy-d9CofBCo,4635
257
258
  reconcile/gql_definitions/common/clusters_with_dms.py,sha256=GJ53P8tgMLh1NfVkaV9_AmaqF9pNUqJZcDkcKzKzUy0,2242
258
- reconcile/gql_definitions/common/clusters_with_peering.py,sha256=9NGjhJW_QWA5XSGGjULdIJdDVObdRqiX2OwEl9zTu4U,11838
259
+ reconcile/gql_definitions/common/clusters_with_peering.py,sha256=B1Hi3u6rZZsl4bDDPMLIcSRI5lNFuh29lPVVTOrRRpQ,11929
259
260
  reconcile/gql_definitions/common/github_orgs.py,sha256=rZ0pDAA2_9hF9N-ykRZIxPtEmczTSjuA_k3nkp0k1W0,2039
260
261
  reconcile/gql_definitions/common/jira_settings.py,sha256=Fmjxhlhr69kc4jkG_0k17fuYlQVucbNex0jXYu83wbY,1990
261
262
  reconcile/gql_definitions/common/jiralert_settings.py,sha256=H96nMg_r2YcOvioj3aIkwqtFrALGSLt7uhbx9jGSUTo,1984
@@ -566,7 +567,7 @@ reconcile/test/test_terraform_cloudflare_resources.py,sha256=1mdSZS-38mtTSg7teJg
566
567
  reconcile/test/test_terraform_cloudflare_users.py,sha256=2VGBtMUhckLPtUnQlHIzpGsCnyVJZPNLFf-ABELkxbQ,27456
567
568
  reconcile/test/test_terraform_repo.py,sha256=INfl-VlUtpV87J0neQt4wliptnX7PKvxLPF-ZgweTFA,12960
568
569
  reconcile/test/test_terraform_resources.py,sha256=8C97yXIEihaQ3DZrtjxLNt4y4G12IOhD01ydm7JjliY,15359
569
- reconcile/test/test_terraform_tgw_attachments.py,sha256=SM6QwogMZNLh0BkUyaxzFafuOLp23-hBtYTu_F53C4I,40922
570
+ reconcile/test/test_terraform_tgw_attachments.py,sha256=pJFmQzUwAn-wKpF6oSkImpdF7WXvcky8iaJiXbjGGgU,41104
570
571
  reconcile/test/test_terraform_users.py,sha256=XOAfGvITCJPI1LTlISmHbA4ONMQMkxYUMTsny7pQCFw,4319
571
572
  reconcile/test/test_terraform_vpc_peerings.py,sha256=bpjCjhmic07cw3XKSHf-2JvmLuWlyQG8laXlC-H7qtI,20796
572
573
  reconcile/test/test_terraform_vpc_peerings_build_desired_state.py,sha256=cHmr1_yhRgfdqlFX6TMw-aiKXebaRv0szl16M9YRJic,49988
@@ -724,8 +725,8 @@ reconcile/utils/sqs_gateway.py,sha256=XNIf3PY4UCPNufP2Ul0UJj3fKlt5larBba-VTT-41F
724
725
  reconcile/utils/state.py,sha256=W0_awkLAPX18hNOF_60o73tkPxDUylqbzYNHfl_sDsk,16386
725
726
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
726
727
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
727
- reconcile/utils/terraform_client.py,sha256=gyTqNyDn0uQISxnfXOWv8HpXSM1-yqvwDiVGIW0jpRk,35215
728
- reconcile/utils/terrascript_aws_client.py,sha256=ctNT-TQSMxNicVIjR1gM2OZWzNc_BG820COSXb_E-CE,278933
728
+ reconcile/utils/terraform_client.py,sha256=LjX2U2E0Dglt2S_KA5jWQ_dVC8sPn4FEAh0xW_d6JTk,35953
729
+ reconcile/utils/terrascript_aws_client.py,sha256=SMsJaOmpn_9QQHhKIN_5ps1zOa9zEcBSsxGOyvF8voU,280492
729
730
  reconcile/utils/three_way_diff_strategy.py,sha256=oQcHXd9LVhirJfoaOBoHUYuZVGfyL2voKr6KVI34zZE,4833
730
731
  reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
731
732
  reconcile/utils/vault.py,sha256=9GSNHku8tw5KM2LKpZ1myWYDLtLGUJgpSnD0DxbzeO0,14956
@@ -836,7 +837,7 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
836
837
  tools/app_interface_reporter.py,sha256=oZPib4HPq0aZ2Zui1QGJGk6qQdfpeihujGDBnSdKyGE,17627
837
838
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
838
839
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
839
- tools/qontract_cli.py,sha256=LDQgwmAg3kzgtPuODqhGgPeVfD_9g8dgiOOVRsJ3YRA,128234
840
+ tools/qontract_cli.py,sha256=5RQemctfItbH3S4TPjX2AmqmS1vCoI5j9zqhxFRJB44,129495
840
841
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
841
842
  tools/template_validation.py,sha256=qpKYaTgk0GOPGa2Ct5_5sKdwIHtCAKIBGzsMPuJU5fw,3371
842
843
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -867,8 +868,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
867
868
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
868
869
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
869
870
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
870
- qontract_reconcile-0.10.1rc1061.dist-info/METADATA,sha256=h9WxXliU1mlCQK_rTCcdKt6xDFcUTN2z9AdiP-QeRzU,2213
871
- qontract_reconcile-0.10.1rc1061.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
872
- qontract_reconcile-0.10.1rc1061.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
873
- qontract_reconcile-0.10.1rc1061.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
874
- qontract_reconcile-0.10.1rc1061.dist-info/RECORD,,
871
+ qontract_reconcile-0.10.1rc1063.dist-info/METADATA,sha256=7-a0ZUIlQEjsDHPbo9WkNXXBEumFIE91sWi6HVm8Umw,2213
872
+ qontract_reconcile-0.10.1rc1063.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
873
+ qontract_reconcile-0.10.1rc1063.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
874
+ qontract_reconcile-0.10.1rc1063.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
875
+ qontract_reconcile-0.10.1rc1063.dist-info/RECORD,,
@@ -0,0 +1,119 @@
1
+ import logging
2
+ from collections.abc import Callable
3
+ from dataclasses import asdict, dataclass, field
4
+
5
+ from reconcile.change_owners.bundle import (
6
+ NoOpFileDiffResolver,
7
+ QontractServerDiff,
8
+ )
9
+ from reconcile.change_owners.change_owners import fetch_change_type_processors
10
+ from reconcile.change_owners.change_types import ChangeTypeContext
11
+ from reconcile.change_owners.changes import aggregate_file_moves, parse_bundle_changes
12
+ from reconcile.utils import gql
13
+ from reconcile.utils.defer import defer
14
+ from reconcile.utils.runtime.integration import (
15
+ PydanticRunParams,
16
+ QontractReconcileIntegration,
17
+ )
18
+ from reconcile.utils.state import init_state
19
+
20
+ QONTRACT_INTEGRATION = "change-log-tracking"
21
+ BUNDLE_DIFFS_OBJ = "bundle-diffs.json"
22
+
23
+
24
+ @dataclass
25
+ class ChangeLogItem:
26
+ commit: str
27
+ change_types: list[str] = field(default_factory=list)
28
+ error: bool = False
29
+
30
+
31
+ @dataclass
32
+ class ChangeLog:
33
+ items: list[ChangeLogItem] = field(default_factory=list)
34
+
35
+
36
+ class ChangeLogIntegrationParams(PydanticRunParams):
37
+ process_existing: bool = False
38
+
39
+
40
+ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationParams]):
41
+ @property
42
+ def name(self) -> str:
43
+ return QONTRACT_INTEGRATION
44
+
45
+ @defer
46
+ def run(
47
+ self,
48
+ dry_run: bool,
49
+ defer: Callable | None = None,
50
+ ) -> None:
51
+ change_type_processors = [
52
+ ctp
53
+ for ctp in fetch_change_type_processors(
54
+ gql.get_api(), NoOpFileDiffResolver()
55
+ )
56
+ if ctp.labels and "change_log_tracking" in ctp.labels
57
+ ]
58
+
59
+ integration_state = init_state(
60
+ integration=self.name,
61
+ )
62
+ if defer:
63
+ defer(integration_state.cleanup)
64
+ diff_state = init_state(
65
+ integration=self.name,
66
+ )
67
+ if defer:
68
+ defer(diff_state.cleanup)
69
+ diff_state.state_path = "bundle-archive/diff"
70
+
71
+ if not self.params.process_existing:
72
+ existing_change_log = ChangeLog(**integration_state.get(BUNDLE_DIFFS_OBJ))
73
+ existing_change_log_items = [
74
+ ChangeLogItem(**i) # type: ignore[arg-type]
75
+ for i in existing_change_log.items
76
+ ]
77
+ change_log = ChangeLog()
78
+ for item in diff_state.ls():
79
+ key = item.lstrip("/")
80
+ commit = key.rstrip(".json")
81
+ if not self.params.process_existing:
82
+ existing_change_log_item = next(
83
+ (i for i in existing_change_log_items if i.commit == commit), None
84
+ )
85
+ if existing_change_log_item:
86
+ logging.debug(f"Found existing commit {commit}")
87
+ change_log.items.append(existing_change_log_item)
88
+ continue
89
+
90
+ logging.info(f"Processing commit {commit}")
91
+ change_log_item = ChangeLogItem(
92
+ commit=commit,
93
+ )
94
+ change_log.items.append(change_log_item)
95
+ obj = diff_state.get(key, None)
96
+ if not obj:
97
+ logging.error(f"Error processing commit {commit}")
98
+ change_log_item.error = True
99
+ continue
100
+ diff = QontractServerDiff(**obj)
101
+ changes = aggregate_file_moves(parse_bundle_changes(diff))
102
+ for change in changes:
103
+ logging.debug(f"Processing change {change}")
104
+ for ctp in change_type_processors:
105
+ logging.info(f"Processing change type {ctp.name}")
106
+ ctx = ChangeTypeContext(
107
+ change_type_processor=ctp,
108
+ context="",
109
+ origin="",
110
+ context_file=change.fileref,
111
+ approvers=[],
112
+ )
113
+ covered_diffs = change.cover_changes(ctx)
114
+ if covered_diffs:
115
+ if ctp.name not in change_log_item.change_types:
116
+ change_log_item.change_types.append(ctp.name)
117
+
118
+ if not dry_run:
119
+ integration_state.add(BUNDLE_DIFFS_OBJ, asdict(change_log), force=True)
@@ -22,6 +22,7 @@ import jinja2
22
22
  import jinja2.meta
23
23
  import jsonpath_ng
24
24
  import networkx
25
+ from pydantic import Json
25
26
 
26
27
  from reconcile.change_owners.approver import (
27
28
  Approver,
@@ -474,6 +475,7 @@ class ChangeTypeProcessor:
474
475
  """
475
476
 
476
477
  name: str
478
+ labels: Json | None
477
479
  description: str
478
480
  priority: ChangeTypePriority
479
481
  context_type: BundleFileType
@@ -715,6 +717,7 @@ def init_change_type_processors(
715
717
  # build raw change-type-processor
716
718
  processors[change_type.name] = ChangeTypeProcessor(
717
719
  name=change_type.name,
720
+ labels=change_type.labels,
718
721
  description=change_type.description,
719
722
  priority=ChangeTypePriority(change_type.priority),
720
723
  context_type=BundleFileType[change_type.context_type.upper()],
@@ -102,7 +102,7 @@ class BundleFileChange:
102
102
  def is_file_creation(self) -> bool:
103
103
  return self.old is None and self.new is not None
104
104
 
105
- def cover_changes(self, change_type_context: ChangeTypeContext) -> None:
105
+ def cover_changes(self, change_type_context: ChangeTypeContext) -> dict[str, Diff]:
106
106
  """
107
107
  Figure out if a ChangeTypeV1 covers detected changes within the BundleFile.
108
108
  Base idea:
@@ -121,7 +121,7 @@ class BundleFileChange:
121
121
  # as a source of approvers
122
122
  if self.metadata_only_change and not self.diffs:
123
123
  self._metadata_only_diff_coverage().coverage.append(change_type_context)
124
- return
124
+ return {}
125
125
 
126
126
  covered_diffs = {}
127
127
  # observe the new state for added fields or list items or entire object sutrees
@@ -139,6 +139,8 @@ class BundleFileChange:
139
139
  )
140
140
  )
141
141
 
142
+ return covered_diffs
143
+
142
144
  def _cover_changes_for_diffs(
143
145
  self,
144
146
  diffs: list[DiffCoverage],
reconcile/cli.py CHANGED
@@ -3557,6 +3557,29 @@ def change_owners(
3557
3557
  )
3558
3558
 
3559
3559
 
3560
+ @integration.command(short_help="Analyze bundle diffs by change types.")
3561
+ @click.option(
3562
+ "--process-existing/--no-process-existing",
3563
+ default=False,
3564
+ help="wait for pending/running pipelines before acting.",
3565
+ )
3566
+ @click.pass_context
3567
+ def change_log_tracking(ctx, process_existing):
3568
+ from reconcile.change_owners.change_log_tracking import (
3569
+ ChangeLogIntegration,
3570
+ ChangeLogIntegrationParams,
3571
+ )
3572
+
3573
+ run_class_integration(
3574
+ ChangeLogIntegration(
3575
+ ChangeLogIntegrationParams(
3576
+ process_existing=process_existing,
3577
+ )
3578
+ ),
3579
+ ctx=ctx.obj,
3580
+ )
3581
+
3582
+
3560
3583
  @integration.command(
3561
3584
  short_help="Configure and enforce glitchtip instance configuration."
3562
3585
  )
@@ -22,6 +22,7 @@ DEFINITION = """
22
22
  query ChangeTypes($name: String) {
23
23
  change_types: change_types_v1(name: $name) {
24
24
  name
25
+ labels
25
26
  description
26
27
  priority
27
28
  contextType
@@ -117,6 +118,7 @@ class ChangeTypeV1_ChangeTypeV1(ConfiguredBaseModel):
117
118
 
118
119
  class ChangeTypeV1(ConfiguredBaseModel):
119
120
  name: str = Field(..., alias="name")
121
+ labels: Optional[Json] = Field(..., alias="labels")
120
122
  description: str = Field(..., alias="description")
121
123
  priority: str = Field(..., alias="priority")
122
124
  context_type: str = Field(..., alias="contextType")
@@ -147,6 +147,7 @@ query ClustersWithPeering {
147
147
  }
148
148
  tags
149
149
  cidrBlock
150
+ cidrBlocks
150
151
  manageSecurityGroups
151
152
  manageRoute53Associations
152
153
  allowPrivateHcpApiAccess
@@ -268,6 +269,7 @@ class ClusterPeeringConnectionAccountTGWV1(ClusterPeeringConnectionV1):
268
269
  account: ClusterPeeringConnectionAccountTGWV1_AWSAccountV1 = Field(..., alias="account")
269
270
  tags: Optional[Json] = Field(..., alias="tags")
270
271
  cidr_block: Optional[str] = Field(..., alias="cidrBlock")
272
+ cidr_blocks: Optional[list[str]] = Field(..., alias="cidrBlocks")
271
273
  manage_security_groups: Optional[bool] = Field(..., alias="manageSecurityGroups")
272
274
  manage_route53_associations: Optional[bool] = Field(..., alias="manageRoute53Associations")
273
275
  allow_private_hcp_api_access: Optional[bool] = Field(..., alias="allowPrivateHcpApiAccess")
@@ -64,11 +64,14 @@ class ValidationError(Exception):
64
64
  pass
65
65
 
66
66
 
67
- class AccountProviderInfo(BaseModel):
67
+ class TGWAccountProviderInfo(BaseModel):
68
68
  name: str
69
69
  uid: str
70
70
  assume_role: str | None
71
71
  assume_region: str
72
+
73
+
74
+ class ClusterAccountProviderInfo(TGWAccountProviderInfo):
72
75
  assume_cidr: str
73
76
 
74
77
 
@@ -79,8 +82,9 @@ class Requester(BaseModel):
79
82
  routes: list[dict] | None
80
83
  rules: list[dict] | None
81
84
  hostedzones: list[str] | None
82
- cidr_block: str
83
- account: AccountProviderInfo
85
+ cidr_block: str | None
86
+ cidr_blocks: list[str]
87
+ account: TGWAccountProviderInfo
84
88
 
85
89
 
86
90
  class Accepter(BaseModel):
@@ -89,7 +93,7 @@ class Accepter(BaseModel):
89
93
  vpc_id: str | None
90
94
  route_table_ids: list[str] | None
91
95
  subnets_id_az: list[dict] | None
92
- account: AccountProviderInfo
96
+ account: ClusterAccountProviderInfo
93
97
  api_security_group_id: str | None
94
98
 
95
99
 
@@ -225,7 +229,7 @@ def _build_account_with_assume_role(
225
229
  region: str,
226
230
  cidr_block: str,
227
231
  ocm: OCM | None,
228
- ) -> AccountProviderInfo:
232
+ ) -> ClusterAccountProviderInfo:
229
233
  account = peer_connection.account
230
234
  # assume_role is the role to assume to provision the
231
235
  # peering connection request, through the accepter AWS account.
@@ -235,7 +239,7 @@ def _build_account_with_assume_role(
235
239
  # there is no OCM at all.
236
240
  if not assume_role:
237
241
  if isinstance(cluster.spec, ClusterSpecROSAV1) and cluster.spec.account:
238
- return AccountProviderInfo(
242
+ return ClusterAccountProviderInfo(
239
243
  name=cluster.spec.account.name,
240
244
  uid=cluster.spec.account.uid,
241
245
  assume_role=assume_role,
@@ -247,7 +251,7 @@ def _build_account_with_assume_role(
247
251
  assume_role = ocm.get_aws_infrastructure_access_terraform_assume_role(
248
252
  cluster.name, account.uid, account.terraform_username
249
253
  )
250
- return AccountProviderInfo(
254
+ return ClusterAccountProviderInfo(
251
255
  name=account.name,
252
256
  uid=account.uid,
253
257
  assume_role=assume_role,
@@ -258,7 +262,7 @@ def _build_account_with_assume_role(
258
262
 
259
263
  def _build_accepter(
260
264
  peer_connection: ClusterPeeringConnectionAccountTGWV1,
261
- account: AccountProviderInfo,
265
+ account: ClusterAccountProviderInfo,
262
266
  region: str,
263
267
  cidr_block: str,
264
268
  awsapi: AWSApi,
@@ -290,11 +294,10 @@ def _build_requester(
290
294
  peer_connection: ClusterPeeringConnectionAccountTGWV1,
291
295
  tgw: Mapping,
292
296
  ) -> Requester:
293
- tgw_account = AccountProviderInfo(
297
+ tgw_account = TGWAccountProviderInfo(
294
298
  name=peer_connection.account.name,
295
299
  uid=peer_connection.account.uid,
296
300
  assume_region=tgw["region"],
297
- assume_cidr=peer_connection.cidr_block,
298
301
  )
299
302
  return Requester(
300
303
  tgw_id=tgw["tgw_id"],
@@ -304,6 +307,7 @@ def _build_requester(
304
307
  rules=tgw.get("rules"),
305
308
  hostedzones=tgw.get("hostedzones"),
306
309
  cidr_block=peer_connection.cidr_block,
310
+ cidr_blocks=peer_connection.cidr_blocks or [],
307
311
  account=tgw_account,
308
312
  )
309
313
 
@@ -161,6 +161,7 @@ def peering_connection_builder(
161
161
  account: ClusterPeeringConnectionAccountTGWV1_AWSAccountV1 | None = None,
162
162
  assume_role: str | None = None,
163
163
  cidr_block: str | None = None,
164
+ cidr_blocks: list[str] | None = None,
164
165
  delete: bool | None = None,
165
166
  ) -> ClusterPeeringConnectionAccountTGWV1:
166
167
  return gql_class_factory(
@@ -172,6 +173,7 @@ def peering_connection_builder(
172
173
  "account": account.dict(by_alias=True) if account is not None else None,
173
174
  "assumeRole": assume_role,
174
175
  "cidrBlock": cidr_block,
176
+ "cidrBlocks": cidr_blocks,
175
177
  "delete": delete,
176
178
  },
177
179
  )
@@ -191,6 +193,7 @@ def account_tgw_connection(
191
193
  account=tgw_connection_account,
192
194
  assume_role=None,
193
195
  cidr_block="172.16.0.0/16",
196
+ cidr_blocks=["10.240.0.0/12"],
194
197
  delete=False,
195
198
  )
196
199
 
@@ -481,6 +484,7 @@ def build_expected_desired_state_item(
481
484
  rules=tgw["rules"],
482
485
  hostedzones=tgw["hostedzones"],
483
486
  cidr_block=connection.cidr_block,
487
+ cidr_blocks=connection.cidr_blocks or [],
484
488
  account=expected_tgw_account,
485
489
  ),
486
490
  accepter=Accepter(
@@ -331,12 +331,28 @@ class TerraformClient: # pylint: disable=too-many-public-methods
331
331
  for resource_change in resource_changes:
332
332
  resource_type = resource_change["type"]
333
333
  resource_name = resource_change["name"]
334
+ resource_address = resource_change["address"]
335
+ resource_previous_address = resource_change.get("previous_address")
334
336
  resource_change = resource_change["change"]
335
337
  actions = resource_change["actions"]
336
338
  for action in actions:
339
+ if resource_previous_address:
340
+ # the resource is being moved/renamed in the TF state
341
+ with self._log_lock:
342
+ logging.info([
343
+ "move/rename",
344
+ name,
345
+ resource_previous_address,
346
+ resource_address,
347
+ ])
348
+
337
349
  if action == "no-op":
338
350
  logging.debug([action, name, resource_type, resource_name])
339
- continue
351
+ if resource_previous_address:
352
+ # apply resource renaming with no-op
353
+ self.increment_apply_count()
354
+ else:
355
+ continue
340
356
  if action == "update" and resource_type == "aws_db_instance":
341
357
  self.validate_db_upgrade(name, resource_name, resource_change)
342
358
  # Ignore RDS modifications that are going to occur during the next
@@ -35,6 +35,7 @@ from sretoolbox.utils import threaded
35
35
  # temporary to create aws_ecrpublic_repository
36
36
  from terrascript import (
37
37
  Backend,
38
+ Block,
38
39
  Data,
39
40
  Module,
40
41
  Output,
@@ -299,6 +300,13 @@ class UnapprovedSecretPathError(Exception):
299
300
  pass
300
301
 
301
302
 
303
+ class Moved(Block):
304
+ """Terraform `moved` block, available since Terraform 1.1"""
305
+
306
+ def __init__(self, fro: str, to: str):
307
+ super().__init__(fro=fro, to=to)
308
+
309
+
302
310
  class aws_ecrpublic_repository(Resource):
303
311
  pass
304
312
 
@@ -1392,17 +1400,32 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1392
1400
  # add routes to existing route tables
1393
1401
  route_table_ids = accepter.route_table_ids
1394
1402
  req_cidr_block = requester.cidr_block
1395
- if route_table_ids and req_cidr_block:
1403
+ req_cidr_blocks = requester.cidr_blocks or []
1404
+ if req_cidr_block:
1405
+ req_cidr_blocks.append(req_cidr_block)
1406
+ if route_table_ids and (req_cidr_block or req_cidr_blocks):
1396
1407
  for route_table_id in route_table_ids:
1397
- values = {
1398
- "provider": "aws." + acc_alias,
1399
- "route_table_id": route_table_id,
1400
- "destination_cidr_block": req_cidr_block,
1401
- "transit_gateway_id": requester.tgw_id,
1402
- }
1403
- route_identifier = f"{identifier}-{route_table_id}"
1404
- tf_resource = aws_route(route_identifier, **values)
1405
- self.add_resource(infra_account_name, tf_resource)
1408
+ for cidr_block in req_cidr_blocks:
1409
+ values = {
1410
+ "provider": "aws." + acc_alias,
1411
+ "route_table_id": route_table_id,
1412
+ "destination_cidr_block": cidr_block,
1413
+ "transit_gateway_id": requester.tgw_id,
1414
+ }
1415
+ # use the cidr block in the resource name to allow re-ordering
1416
+ cidr_id = cidr_block.replace(".", "-").replace("/", "_")
1417
+ route_identifier = (
1418
+ f"{identifier}-{route_table_id}-dest-{cidr_id}"
1419
+ )
1420
+ tf_resource = aws_route(route_identifier, **values)
1421
+ self.add_resource(infra_account_name, tf_resource)
1422
+ if req_cidr_block:
1423
+ req_cidr_id = req_cidr_block.replace(".", "-").replace("/", "_")
1424
+ moved = Moved(
1425
+ fro=f"aws_route.{identifier}-{route_table_id}",
1426
+ to=f"aws_route.{identifier}-{route_table_id}-dest-{req_cidr_id}",
1427
+ )
1428
+ self.add_moved(infra_account_name, moved)
1406
1429
 
1407
1430
  # add routes to peered transit gateways in the requester's
1408
1431
  # account to achieve global routing from all regions
@@ -4084,6 +4107,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4084
4107
  with self.locks[account]:
4085
4108
  self.tss[account].add(tf_resource)
4086
4109
 
4110
+ def add_moved(self, account: str, moved: Moved):
4111
+ if account not in self.locks:
4112
+ logging.debug(
4113
+ f"integration {self.integration} is disabled for account {account}. "
4114
+ "can not add resource"
4115
+ )
4116
+ return
4117
+ with self.locks[account]:
4118
+ self.tss[account].setdefault("moved", []).append({
4119
+ "from": moved.fro,
4120
+ "to": moved.to,
4121
+ })
4122
+
4087
4123
  def dump(
4088
4124
  self,
4089
4125
  print_to_file: str | None = None,
tools/qontract_cli.py CHANGED
@@ -47,6 +47,12 @@ from reconcile.aus.base import (
47
47
  )
48
48
  from reconcile.aus.models import OrganizationUpgradeSpec
49
49
  from reconcile.change_owners.bundle import NoOpFileDiffResolver
50
+ from reconcile.change_owners.change_log_tracking import (
51
+ ChangeLog,
52
+ ChangeLogIntegration,
53
+ ChangeLogIntegrationParams,
54
+ ChangeLogItem,
55
+ )
50
56
  from reconcile.change_owners.change_owners import (
51
57
  fetch_change_type_processors,
52
58
  fetch_self_service_roles,
@@ -85,6 +91,7 @@ from reconcile.jenkins_job_builder import init_jjb
85
91
  from reconcile.slack_base import slackapi_from_queries
86
92
  from reconcile.status_board import StatusBoardExporterIntegration
87
93
  from reconcile.typed_queries.alerting_services_settings import get_alerting_services
94
+ from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
88
95
  from reconcile.typed_queries.app_interface_vault_settings import (
89
96
  get_app_interface_vault_settings,
90
97
  )
@@ -2857,6 +2864,35 @@ def container_image_details(ctx):
2857
2864
  print_output(ctx.obj["options"], data, columns)
2858
2865
 
2859
2866
 
2867
+ @get.command
2868
+ @click.pass_context
2869
+ def change_log_tracking(ctx):
2870
+ repo_url = get_app_interface_repo_url()
2871
+ change_types = fetch_change_type_processors(gql.get_api(), NoOpFileDiffResolver())
2872
+ state = init_state(
2873
+ integration=ChangeLogIntegration(ChangeLogIntegrationParams()).name
2874
+ )
2875
+ change_log = ChangeLog(**state.get("bundle-diffs.json"))
2876
+ data: list[dict[str, str]] = []
2877
+ for item in change_log.items:
2878
+ change_log_item = ChangeLogItem(**item)
2879
+ commit = change_log_item.commit
2880
+ covered_change_types_descriptions = [
2881
+ ct.description
2882
+ for ct in change_types
2883
+ if ct.name in change_log_item.change_types
2884
+ ]
2885
+ item = {
2886
+ "commit": f"[{commit}]({repo_url}/commit/{commit})",
2887
+ "changes": ", ".join(covered_change_types_descriptions),
2888
+ "error": change_log_item.error,
2889
+ }
2890
+ data.append(item)
2891
+
2892
+ columns = ["commit", "changes", "error"]
2893
+ print_output(ctx.obj["options"], data, columns)
2894
+
2895
+
2860
2896
  @root.group(name="set")
2861
2897
  @output
2862
2898
  @click.pass_context