qontract-reconcile 0.10.1rc789__py3-none-any.whl → 0.10.1rc791__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.1rc789
3
+ Version: 0.10.1rc791
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
@@ -248,7 +248,7 @@ reconcile/gql_definitions/common/pagerduty_instances.py,sha256=qcbOSUNwmeRcBetJ2
248
248
  reconcile/gql_definitions/common/pgp_reencryption_settings.py,sha256=NPLmO6J-zSu5B9QiYbDezLHY3TuOO9ihRBV-Zr84R9w,2259
249
249
  reconcile/gql_definitions/common/pipeline_providers.py,sha256=JJgmmghqLIwjKOdcWYHPnf4PDgAq4GF7046i0ozrqgI,9127
250
250
  reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
251
- reconcile/gql_definitions/common/saas_files.py,sha256=B7HE_jV5ky8AFxOCleSg2RYDrtUaV99DYa2If8wyvHs,15926
251
+ reconcile/gql_definitions/common/saas_files.py,sha256=0F1dXJo7B37S2i5RQSvISOQOBlu2U2ue1rRsK5SZ4VU,16005
252
252
  reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=gcTU9jrsNq9-HX-oOkj-nEZKYFTRytDHLs4SpEs93aw,2755
253
253
  reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
254
254
  reconcile/gql_definitions/common/smtp_client_settings.py,sha256=JU6t6D-Qj-z1gLlgUiHKe0W7AxWQdty9jlv-ig_43tM,2248
@@ -409,9 +409,9 @@ reconcile/rhidp/sso_client/metrics.py,sha256=Tq7tSOsqL3XdcPUdozxqzSPIodUeOV87UCT
409
409
  reconcile/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
410
410
  reconcile/saas_auto_promotions_manager/integration.py,sha256=8IXLEvpblgZRr2UPfTsaLZIUDJOdJaoakhqLBkGU_Es,6750
411
411
  reconcile/saas_auto_promotions_manager/meta.py,sha256=76Jp50r6Y_KyJoXFfSjrt5YrCtXyg_A4FXXxHYiS3TE,161
412
- reconcile/saas_auto_promotions_manager/publisher.py,sha256=psrthZGgCQDUO3rwQjKSBMlwcTgfij6sxdebGuxkNv4,2739
412
+ reconcile/saas_auto_promotions_manager/publisher.py,sha256=am8b0uLZIhWjSAgUHPtSOu2fRpa5wXaMl_WnVe4gv2k,3009
413
413
  reconcile/saas_auto_promotions_manager/s3_exporter.py,sha256=IKlVWZmiPnvl7sKeF6JgAlhXZe5CovKTxQc0SNkNSx4,2583
414
- reconcile/saas_auto_promotions_manager/subscriber.py,sha256=NPhlagNF8om7ikrjRlYNSQ2Ra7wgW_3-OlEWapnjtW0,9405
414
+ reconcile/saas_auto_promotions_manager/subscriber.py,sha256=SL-LsUhdhOLnlSremXStIlpVCZx-0zLcTkxSJPIScdQ,10338
415
415
  reconcile/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
416
416
  reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py,sha256=lHIULPE8nmPN9pJVfZFdD0l48EB20o4cAy4owahJenw,7848
417
417
  reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=isY8frVsL3PlcdZmdZ4O0qyp76oczl4DUMX9uMArs5Y,1222
@@ -422,14 +422,14 @@ reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py,sha256
422
422
  reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py,sha256=-qGQOh6Jdp4lomNDij3zWVC0pl6uPHFWS5Woqcp5HQk,410
423
423
  reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=IZ7cuH6uOi7f0aIPVi1irBmP0CIK5vmEuhKBJz4YA1s,7235
424
424
  reconcile/saas_auto_promotions_manager/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
- reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=txisb7NT7u5vpmZqRK3R4bpHDiJ7IUyNt9FZfvgm9MA,7998
425
+ reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=ZZ7JL6VPfKasq-XXi6CL2UZ89jOcC9uwLW1e8LMvgws,8187
426
426
  reconcile/skupper_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
427
427
  reconcile/skupper_network/integration.py,sha256=178Q9RSYuZ9NmrCK4jRMLMekrewUaaRdclccI6zBsQ8,10786
428
428
  reconcile/skupper_network/models.py,sha256=DNTI7HZv-rqY42GIIxyRuvroHLvdH6rJerjIq9lj3RU,6663
429
429
  reconcile/skupper_network/reconciler.py,sha256=XS-1oKBr_1l3dYUAVqUH6gCHg1G5ZuOfY_7fgGVAiFA,9996
430
430
  reconcile/skupper_network/site_controller.py,sha256=A3K-62BjJ5HiFVydV0ouGoD1NwrO7XhAH15BHAcS9fk,1550
431
431
  reconcile/statuspage/__init__.py,sha256=o9vR6sp3ARDQFZrbCEShelTxjF1XgfLaElK_QVt_248,261
432
- reconcile/statuspage/atlassian.py,sha256=zXsO9qx_pqfk2TDSoa1JSv6UvY2nVsJAp6rRWuEcG24,13258
432
+ reconcile/statuspage/atlassian.py,sha256=IxtGHd4GXYlJ2Qt3k-MfylM-SIYS7DYaCpjtQvPAmbY,13758
433
433
  reconcile/statuspage/integration.py,sha256=---tzyl381RddAkIhXb7n3ySjUhuX7FBBI152SYsRfk,3654
434
434
  reconcile/statuspage/page.py,sha256=cJH2sDA8jiAmSdaDitQqNjkyDq_UP2w3s7eauCi-yt4,3740
435
435
  reconcile/statuspage/state.py,sha256=HD9EOoKm_nEqCMLIwW809En3cq5VhyzKJPUbsh-bae8,1617
@@ -510,7 +510,7 @@ reconcile/test/test_quay_repos.py,sha256=TdkcRF_a8PLp01Kti9eZZN-vGup2yPBT4Iba3k0
510
510
  reconcile/test/test_queries.py,sha256=SpH3RmNpBjEr_ne3VjAMCgKK8RE1z1zo7bypkT5uoO4,1946
511
511
  reconcile/test/test_repo_owners.py,sha256=uRYMLbMmh-9usF0TerabZTZV-Z1CS4I6ybT-LQqCLe8,1423
512
512
  reconcile/test/test_requests_sender.py,sha256=7fd9C2kEFS0-CYtlsif66N1kO9c44pzuBPAJKR9igqU,5385
513
- reconcile/test/test_saasherder.py,sha256=1_GyiXxxNqKSKE7PrtFJL7tUFg77d1oQPZzNBZW-DLQ,47042
513
+ reconcile/test/test_saasherder.py,sha256=hSZk34aZFq-wspT-kJmFuHjh8ztSr7IzGc5QdRVrsJ8,47164
514
514
  reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
515
515
  reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
516
516
  reconcile/test/test_slack_base.py,sha256=gpbWOLNxMMX6fyAbs1JakhLTnwfedb3f7WpUae4tQZE,5060
@@ -536,7 +536,7 @@ reconcile/test/test_version_bump.py,sha256=q6-3Y1roriI6YWpFwaHOMN7emEP3yL33sh_0V
536
536
  reconcile/test/test_vpc_peerings_validator.py,sha256=dFSmjc_dMN2GqMbntCFpa7PUZmyYuQ9DKffh-T5wmxM,6639
537
537
  reconcile/test/test_wrong_region.py,sha256=7KzL7OaICQ9Z3DW27zt_ykMN7_87owAFC-2CYjvGoyA,2138
538
538
  reconcile/test/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
539
- reconcile/test/saas_auto_promotions_manager/conftest.py,sha256=4BtuxVZ0Lsmdp6AhG8kbtowkwG7e-pSjIKv35Wm1hI0,5803
539
+ reconcile/test/saas_auto_promotions_manager/conftest.py,sha256=GdFWjuNMrwB9tp0ZG6O9OZnI2GTtBK5cO4DCz_liBtc,5980
540
540
  reconcile/test/saas_auto_promotions_manager/test_integration_test.py,sha256=S30eXJSy2Vc3YLbCP7AfLkOiFGUVoKhEvEBL5vwnbfg,1848
541
541
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
542
542
  reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -785,8 +785,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
785
785
  tools/test/test_qontract_cli.py,sha256=w2l4BHB09k1d-BGJ1jBUNCqDv7zkqYrMHojQXg-21kQ,4155
786
786
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
787
787
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
788
- qontract_reconcile-0.10.1rc789.dist-info/METADATA,sha256=P-jDTPzT-xpXJCJ88nSB36g41PoUE7_ZKeaczLFTwgo,2364
789
- qontract_reconcile-0.10.1rc789.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
790
- qontract_reconcile-0.10.1rc789.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
791
- qontract_reconcile-0.10.1rc789.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
792
- qontract_reconcile-0.10.1rc789.dist-info/RECORD,,
788
+ qontract_reconcile-0.10.1rc791.dist-info/METADATA,sha256=fZl69FcdS3wj97ZwyE1VwTV42kLiyLmoNcZ0yrB7T9Q,2364
789
+ qontract_reconcile-0.10.1rc791.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
790
+ qontract_reconcile-0.10.1rc791.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
791
+ qontract_reconcile-0.10.1rc791.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
792
+ qontract_reconcile-0.10.1rc791.dist-info/RECORD,,
@@ -254,6 +254,7 @@ query SaasFiles {
254
254
  auto
255
255
  publish
256
256
  subscribe
257
+ soakDays
257
258
  promotion_data {
258
259
  channel
259
260
  data {
@@ -452,6 +453,7 @@ class SaasResourceTemplateTargetPromotionV1(ConfiguredBaseModel):
452
453
  auto: Optional[bool] = Field(..., alias="auto")
453
454
  publish: Optional[list[str]] = Field(..., alias="publish")
454
455
  subscribe: Optional[list[str]] = Field(..., alias="subscribe")
456
+ soak_days: Optional[int] = Field(..., alias="soakDays")
455
457
  promotion_data: Optional[list[PromotionDataV1]] = Field(..., alias="promotion_data")
456
458
 
457
459
 
@@ -1,4 +1,5 @@
1
1
  from dataclasses import dataclass
2
+ from datetime import datetime
2
3
  from typing import Optional
3
4
 
4
5
  from reconcile.utils.promotion_state import PromotionState
@@ -17,6 +18,7 @@ class DeploymentInfo:
17
18
  success: bool
18
19
  saas_file: str
19
20
  target_config_hash: str
21
+ check_in: Optional[datetime]
20
22
 
21
23
 
22
24
  class Publisher:
@@ -80,8 +82,14 @@ class Publisher:
80
82
  ):
81
83
  continue
82
84
 
85
+ check_in = (
86
+ datetime.fromisoformat(promotion_data.check_in)
87
+ if promotion_data.check_in
88
+ else None
89
+ )
83
90
  self.deployment_info_by_channel[channel] = DeploymentInfo(
84
91
  success=promotion_data.success,
85
92
  saas_file=promotion_data.saas_file,
86
93
  target_config_hash=promotion_data.target_config_hash,
94
+ check_in=check_in,
87
95
  )
@@ -43,6 +43,7 @@ class Subscriber:
43
43
  target_namespace: SaasTargetNamespace,
44
44
  use_target_config_hash: bool,
45
45
  uid: str,
46
+ soak_days: int,
46
47
  ):
47
48
  self.saas_name = saas_name
48
49
  self.template_name = template_name
@@ -54,6 +55,7 @@ class Subscriber:
54
55
  self.desired_hashes: list[ConfigHash] = []
55
56
  self.target_namespace = target_namespace
56
57
  self.uid = uid
58
+ self.soak_days = soak_days
57
59
  self._content_hash = ""
58
60
  self._use_target_config_hash = use_target_config_hash
59
61
 
@@ -85,9 +87,10 @@ class Subscriber:
85
87
  use_target_config_hash=data["5"],
86
88
  target_namespace=SaasTargetNamespace(**data["6"]),
87
89
  uid=data["7"],
90
+ soak_days=data["8"],
88
91
  )
89
- subscriber.desired_hashes = data["8"]
90
- subscriber.desired_ref = data["9"]
92
+ subscriber.desired_hashes = data["9"]
93
+ subscriber.desired_ref = data["10"]
91
94
  return subscriber
92
95
 
93
96
  def to_exportable_dict(self) -> dict[str, Any]:
@@ -103,8 +106,9 @@ class Subscriber:
103
106
  data["5"] = self._use_target_config_hash
104
107
  data["6"] = self.target_namespace.dict(by_alias=True)
105
108
  data["7"] = self.uid
106
- data["8"] = self.desired_hashes
107
- data["9"] = self.desired_ref
109
+ data["8"] = self.soak_days
110
+ data["9"] = self.desired_hashes
111
+ data["10"] = self.desired_ref
108
112
  return data
109
113
 
110
114
  def __eq__(self, other: object) -> bool:
@@ -143,6 +147,24 @@ class Subscriber:
143
147
  return None
144
148
  return deployment_info
145
149
 
150
+ def _passed_accumulated_soak_days(self) -> bool:
151
+ """
152
+ We accumulate the time a ref is running on all publishers for this subscriber.
153
+ We compare that accumulated time with the soak_days setting of the subscriber.
154
+ """
155
+ # now = datetime.now(timezone.utc)
156
+ # delta = timedelta(days=0)
157
+ # for channel in self.channels:
158
+ # for publisher in channel.publishers:
159
+ # deployed_at = publisher.deployment_info_by_channel.get(
160
+ # channel.name
161
+ # ).check_in
162
+ # if not deployed_at:
163
+ # continue
164
+ # delta += now - deployed_at
165
+ # return delta >= timedelta(days=self.soak_days)
166
+ return True
167
+
146
168
  def _compute_desired_ref(self) -> None:
147
169
  """
148
170
  Compute the desired reference for this subscriber.
@@ -174,8 +196,12 @@ class Subscriber:
174
196
  self.target_file_path,
175
197
  publisher_refs,
176
198
  )
177
- if len(publisher_refs) != 1 or any_bad_deployment:
178
- # We keep current state due to issues/mismatches in publishers
199
+ if (
200
+ len(publisher_refs) != 1
201
+ or any_bad_deployment
202
+ or not self._passed_accumulated_soak_days()
203
+ ):
204
+ # We keep current state
179
205
  self.desired_ref = self.ref
180
206
  else:
181
207
  # We have a common single publisher ref w/o any deployment issues
@@ -95,6 +95,9 @@ class SaasFilesInventory:
95
95
  continue
96
96
  if not target.promotion.auto:
97
97
  continue
98
+ soak_days = (
99
+ target.promotion.soak_days if target.promotion.soak_days else 0
100
+ )
98
101
  subscriber = Subscriber(
99
102
  uid=target.uid(
100
103
  parent_saas_file_name=saas_file.name,
@@ -105,6 +108,7 @@ class SaasFilesInventory:
105
108
  target_file_path=file_path,
106
109
  ref=target.ref,
107
110
  target_namespace=target.namespace,
111
+ soak_days=soak_days,
108
112
  # Note: this will be refactored at a later point.
109
113
  # https://issues.redhat.com/browse/APPSRE-7516
110
114
  use_target_config_hash=bool(saas_file.publish_job_logs),
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import time
2
3
  from typing import (
3
4
  Any,
4
5
  Optional,
@@ -8,6 +9,8 @@ from typing import (
8
9
  import requests
9
10
  import statuspageio # type: ignore
10
11
  from pydantic import BaseModel
12
+ from requests import Response
13
+ from sretoolbox.utils import retry
11
14
 
12
15
  from reconcile.gql_definitions.statuspage.statuspages import StatusPageV1
13
16
  from reconcile.statuspage.page import (
@@ -60,25 +63,35 @@ class LegacyLibAtlassianAPI:
60
63
  self.page_id = page_id
61
64
  self.api_url = api_url
62
65
  self.token = token
66
+ self.auth_headers = {"Authorization": f"OAuth {self.token}"}
63
67
  self._client = statuspageio.Client(
64
68
  api_key=self.token, page_id=self.page_id, organization_id="unset"
65
69
  )
66
70
 
71
+ @retry(max_attempts=10)
72
+ def _do_get(self, url: str, params: dict[str, Any]) -> Response:
73
+ response = requests.get(
74
+ url, params=params, headers=self.auth_headers, timeout=30
75
+ )
76
+ response.raise_for_status()
77
+ return response
78
+
67
79
  def list_components(self) -> list[AtlassianRawComponent]:
68
80
  url = f"{self.api_url}/v1/pages/{self.page_id}/components"
69
- headers = {"Authorization": f"OAuth {self.token}"}
70
81
  all_components: list[AtlassianRawComponent] = []
71
82
  page = 1
72
83
  per_page = 100
73
84
  while True:
74
85
  params = {"page": page, "per_page": per_page}
75
- response = requests.get(url, params=params, headers=headers)
76
- response.raise_for_status()
86
+ response = self._do_get(url, params=params)
77
87
  components = [AtlassianRawComponent(**c) for c in response.json()]
78
88
  all_components += components
79
89
  if len(components) < per_page:
80
90
  break
81
91
  page += 1
92
+ # https://developer.statuspage.io/#section/Rate-Limiting
93
+ # Each API token is limited to 1 request / second as measured on a 60 second rolling window
94
+ time.sleep(1)
82
95
 
83
96
  return all_components
84
97
 
@@ -5,6 +5,7 @@ from collections.abc import (
5
5
  Mapping,
6
6
  MutableMapping,
7
7
  )
8
+ from datetime import datetime, timezone
8
9
  from typing import Any
9
10
  from unittest.mock import (
10
11
  MagicMock,
@@ -139,6 +140,7 @@ def subscriber_builder(
139
140
  success=publisher_data.get("SUCCESSFUL_DEPLOYMENT", True),
140
141
  target_config_hash=publisher_data.get("CONFIG_HASH", ""),
141
142
  saas_file=publisher_name,
143
+ check_in=publisher_data.get("CHECK_IN", datetime.now(timezone.utc)),
142
144
  )
143
145
  channel.publishers.append(publisher)
144
146
  channels.append(channel)
@@ -155,6 +157,7 @@ def subscriber_builder(
155
157
  target_file_path=data.get("TARGET_FILE_PATH", ""),
156
158
  template_name="",
157
159
  use_target_config_hash=data.get("USE_TARGET_CONFIG_HASH", True),
160
+ soak_days=data.get("SOAK_DAYS", 0),
158
161
  )
159
162
  subscriber.channels = channels
160
163
  subscriber.config_hashes_by_channel_name = cur_config_hashes_by_channel
@@ -160,7 +160,11 @@ class TestSaasFileValid(TestCase):
160
160
  self.saas_file.resource_templates[0].targets[
161
161
  1
162
162
  ].promotion = SaasResourceTemplateTargetPromotionV1(
163
- auto=True, publish=None, subscribe=None, promotion_data=None
163
+ auto=True,
164
+ publish=None,
165
+ subscribe=None,
166
+ promotion_data=None,
167
+ soakDays=0,
164
168
  )
165
169
  saasherder = SaasHerder(
166
170
  [self.saas_file],
@@ -180,7 +184,11 @@ class TestSaasFileValid(TestCase):
180
184
  self.saas_file.resource_templates[0].targets[
181
185
  1
182
186
  ].promotion = SaasResourceTemplateTargetPromotionV1(
183
- auto=True, publish=None, subscribe=None, promotion_data=None
187
+ auto=True,
188
+ publish=None,
189
+ subscribe=None,
190
+ promotion_data=None,
191
+ soakDays=0,
184
192
  )
185
193
  saasherder = SaasHerder(
186
194
  [self.saas_file],