qontract-reconcile 0.10.1rc813__py3-none-any.whl → 0.10.1rc815__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.
- {qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/RECORD +13 -13
- reconcile/gql_definitions/maintenance/maintenances.py +6 -0
- reconcile/gql_definitions/statuspage/statuspages.py +14 -0
- reconcile/statuspage/atlassian.py +69 -23
- reconcile/statuspage/integrations/maintenances.py +12 -2
- reconcile/statuspage/page.py +56 -4
- reconcile/utils/secret_reader.py +1 -6
- reconcile/utils/vault.py +0 -1
- tools/qontract_cli.py +1 -1
- {qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc815
|
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
|
{qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/RECORD
RENAMED
@@ -319,7 +319,7 @@ reconcile/gql_definitions/ldap_groups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
319
319
|
reconcile/gql_definitions/ldap_groups/roles.py,sha256=goGDnkBBFy0mdLsXqL9qlSLPCFd9rwiD1rrsIH-6nZQ,2888
|
320
320
|
reconcile/gql_definitions/ldap_groups/settings.py,sha256=KR6eKqXQWVYZAUEdatL1RCARaTOWl9X-QmxEMVjVNDE,2227
|
321
321
|
reconcile/gql_definitions/maintenance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
322
|
-
reconcile/gql_definitions/maintenance/maintenances.py,sha256=
|
322
|
+
reconcile/gql_definitions/maintenance/maintenances.py,sha256=ObAoOHUrTxL1l8CUhdOACc1s49Pt5j1fsErXE49fs1g,3042
|
323
323
|
reconcile/gql_definitions/membershipsources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
324
324
|
reconcile/gql_definitions/membershipsources/roles.py,sha256=d3nv3GLsj_eKgwB1glsiK6smpC4i16WO3dU5rIdRg94,3678
|
325
325
|
reconcile/gql_definitions/ocm_labels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -354,7 +354,7 @@ reconcile/gql_definitions/slo_documents/slo_documents.py,sha256=pOrm9NXAonlo6Lxq
|
|
354
354
|
reconcile/gql_definitions/status_board/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
355
355
|
reconcile/gql_definitions/status_board/status_board.py,sha256=vHEzncabujkqbjJ-ibMYNJTODgTc4DMf4y6TW3I_7II,4700
|
356
356
|
reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
357
|
-
reconcile/gql_definitions/statuspage/statuspages.py,sha256=
|
357
|
+
reconcile/gql_definitions/statuspage/statuspages.py,sha256=fCTJCcXQkIuoSDLcW_kj85qioaEuk2mygiDcggakYUw,5171
|
358
358
|
reconcile/gql_definitions/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
359
359
|
reconcile/gql_definitions/templating/template_collection.py,sha256=lS0vzEKV2ZrzOqOEriqpy0yBgKjb2Ftrzgx6PIH46_4,3310
|
360
360
|
reconcile/gql_definitions/templating/templates.py,sha256=ejAvQ13zfNMQTz3FWtRUic6dSvio3aAgBKEqt600hbk,2821
|
@@ -436,14 +436,14 @@ reconcile/skupper_network/models.py,sha256=DNTI7HZv-rqY42GIIxyRuvroHLvdH6rJerjIq
|
|
436
436
|
reconcile/skupper_network/reconciler.py,sha256=XS-1oKBr_1l3dYUAVqUH6gCHg1G5ZuOfY_7fgGVAiFA,9996
|
437
437
|
reconcile/skupper_network/site_controller.py,sha256=A3K-62BjJ5HiFVydV0ouGoD1NwrO7XhAH15BHAcS9fk,1550
|
438
438
|
reconcile/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
439
|
-
reconcile/statuspage/atlassian.py,sha256=
|
439
|
+
reconcile/statuspage/atlassian.py,sha256=odYbYX8l3oST7AYm66Q4MiaxSbvDzsqEO6VBYJggxkc,17420
|
440
440
|
reconcile/statuspage/integration.py,sha256=hsazrQMceJbr61nEkJLxJbHhudTGtFuH0mlCo66-2ug,711
|
441
|
-
reconcile/statuspage/page.py,sha256=
|
441
|
+
reconcile/statuspage/page.py,sha256=WHDwV2PXEo4WwI2EgPOkS6j_T7geZEDXTgSaqpDo75U,5101
|
442
442
|
reconcile/statuspage/state.py,sha256=HD9EOoKm_nEqCMLIwW809En3cq5VhyzKJPUbsh-bae8,1617
|
443
443
|
reconcile/statuspage/status.py,sha256=mfRJ_tW7jM4_Vy_1cc8C0fKJEoA2GwrA3gJeV1KImAw,2834
|
444
444
|
reconcile/statuspage/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
445
445
|
reconcile/statuspage/integrations/components.py,sha256=49KHd_E9AdRvcEA6n75q1McZv2LfN-hRsW-WA7dgw9g,2651
|
446
|
-
reconcile/statuspage/integrations/maintenances.py,sha256=
|
446
|
+
reconcile/statuspage/integrations/maintenances.py,sha256=7gmMS9HwORMWMMBtib6pNGLXR6sAH2aW-HXKHJn6BN4,3083
|
447
447
|
reconcile/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
448
448
|
reconcile/templates/aws_access_key_email.j2,sha256=2MUr1ERmyISzKgHqsWYLd-1Wbl-peUa-FsGUS-JLUFc,238
|
449
449
|
reconcile/templates/email.yml.j2,sha256=OZgczNRgXPj2gVYTgwQyHAQrMGu7xp-e4W1rX19GcrU,690
|
@@ -665,7 +665,7 @@ reconcile/utils/raw_github_api.py,sha256=ZHC-SZuAyRe1zaMoOU7Krt1-zecDxENd9c_NzQY
|
|
665
665
|
reconcile/utils/repo_owners.py,sha256=j-pUjc9PuDzq7KpjNLpnhqfU8tUG4nj2WMhFp4ick7g,6629
|
666
666
|
reconcile/utils/rest_api_base.py,sha256=X5o4idyRCDzwnF5xFwmjyoaHmM1tXSZnykTA54Z7D2Q,4006
|
667
667
|
reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
|
668
|
-
reconcile/utils/secret_reader.py,sha256=
|
668
|
+
reconcile/utils/secret_reader.py,sha256=7g4TuBxkOl2NgsuZUCRcdI_hKLP3JhXlY1byBSxWU3A,10305
|
669
669
|
reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
|
670
670
|
reconcile/utils/sharding.py,sha256=gkYf0lD3IUKQPEmdRJZ70mdDT1c9qWjbdP7evRsUis4,839
|
671
671
|
reconcile/utils/slack_api.py,sha256=2t9jeCS7V3sHSMk-ByRcjmh-2uVvGELCfJqm1nu_hKI,17395
|
@@ -679,7 +679,7 @@ reconcile/utils/terrascript_aws_client.py,sha256=VlvIHgrZRiMFVgx6a8ZHxoiJoDwqbtO
|
|
679
679
|
reconcile/utils/three_way_diff_strategy.py,sha256=nyqeQsLCoPI6e16k2CF3b9KNgQLU-rPf5RtfdUfVMwE,4468
|
680
680
|
reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
|
681
681
|
reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,3601
|
682
|
-
reconcile/utils/vault.py,sha256=
|
682
|
+
reconcile/utils/vault.py,sha256=AYGG5aDJ7CSVhTFdZowfEg3iSQWenoAt676aGjHQMX8,14978
|
683
683
|
reconcile/utils/vaultsecretref.py,sha256=3Ed2uBy36TzSvL0B-l4FoWQqB2SbBKDKEuUPIO608Bo,931
|
684
684
|
reconcile/utils/vcs.py,sha256=iiAWQXNftKIRoakXEOPT6ubB_ybSuInIQ6jcMxa_NKk,8558
|
685
685
|
reconcile/utils/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -779,7 +779,7 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
|
|
779
779
|
tools/app_interface_reporter.py,sha256=upA-J-n-HXHKVDINRuMR7vTt-iJvQORKUVi9D3leQto,17738
|
780
780
|
tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
|
781
781
|
tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
|
782
|
-
tools/qontract_cli.py,sha256=
|
782
|
+
tools/qontract_cli.py,sha256=PiT1dKAegpt_S-rALEQNEG7CKcgY5sPOY6haZEnxBzs,114860
|
783
783
|
tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
|
784
784
|
tools/template_validation.py,sha256=-U-lTGeLaci8yWPEblCJeev2DOlY1jM9QOOh-O1zts8,3376
|
785
785
|
tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -806,8 +806,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
806
806
|
tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
|
807
807
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
808
808
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
809
|
-
qontract_reconcile-0.10.
|
810
|
-
qontract_reconcile-0.10.
|
811
|
-
qontract_reconcile-0.10.
|
812
|
-
qontract_reconcile-0.10.
|
813
|
-
qontract_reconcile-0.10.
|
809
|
+
qontract_reconcile-0.10.1rc815.dist-info/METADATA,sha256=n6ChIS4befcxQkIjGA6Bc90CIJ7DeX9szDlvPFaV5Hc,2314
|
810
|
+
qontract_reconcile-0.10.1rc815.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
811
|
+
qontract_reconcile-0.10.1rc815.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
812
|
+
qontract_reconcile-0.10.1rc815.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
813
|
+
qontract_reconcile-0.10.1rc815.dist-info/RECORD,,
|
@@ -34,6 +34,9 @@ query Maintenances {
|
|
34
34
|
page {
|
35
35
|
name
|
36
36
|
}
|
37
|
+
remindSubscribers
|
38
|
+
notifySubscribersOnStart
|
39
|
+
notifySubscribersOnCompletion
|
37
40
|
}
|
38
41
|
}
|
39
42
|
}
|
@@ -61,6 +64,9 @@ class StatusPageV1(ConfiguredBaseModel):
|
|
61
64
|
|
62
65
|
class MaintenanceStatuspageAnnouncementV1(MaintenanceAnnouncementV1):
|
63
66
|
page: StatusPageV1 = Field(..., alias="page")
|
67
|
+
remind_subscribers: Optional[bool] = Field(..., alias="remindSubscribers")
|
68
|
+
notify_subscribers_on_start: Optional[bool] = Field(..., alias="notifySubscribersOnStart")
|
69
|
+
notify_subscribers_on_completion: Optional[bool] = Field(..., alias="notifySubscribersOnCompletion")
|
64
70
|
|
65
71
|
|
66
72
|
class MaintenanceV1(ConfiguredBaseModel):
|
@@ -61,12 +61,18 @@ query StatusPages {
|
|
61
61
|
message
|
62
62
|
scheduledStart
|
63
63
|
scheduledEnd
|
64
|
+
affectedServices {
|
65
|
+
name
|
66
|
+
}
|
64
67
|
announcements {
|
65
68
|
provider
|
66
69
|
... on MaintenanceStatuspageAnnouncement_v1 {
|
67
70
|
page {
|
68
71
|
name
|
69
72
|
}
|
73
|
+
remindSubscribers
|
74
|
+
notifySubscribersOnStart
|
75
|
+
notifySubscribersOnCompletion
|
70
76
|
}
|
71
77
|
}
|
72
78
|
}
|
@@ -109,6 +115,10 @@ class StatusPageComponentV1(ConfiguredBaseModel):
|
|
109
115
|
status_config: Optional[list[Union[ManualStatusProviderV1, StatusProviderV1]]] = Field(..., alias="status_config")
|
110
116
|
|
111
117
|
|
118
|
+
class MaintenanceV1_AppV1(ConfiguredBaseModel):
|
119
|
+
name: str = Field(..., alias="name")
|
120
|
+
|
121
|
+
|
112
122
|
class MaintenanceAnnouncementV1(ConfiguredBaseModel):
|
113
123
|
provider: str = Field(..., alias="provider")
|
114
124
|
|
@@ -119,6 +129,9 @@ class MaintenanceStatuspageAnnouncementV1_StatusPageV1(ConfiguredBaseModel):
|
|
119
129
|
|
120
130
|
class MaintenanceStatuspageAnnouncementV1(MaintenanceAnnouncementV1):
|
121
131
|
page: MaintenanceStatuspageAnnouncementV1_StatusPageV1 = Field(..., alias="page")
|
132
|
+
remind_subscribers: Optional[bool] = Field(..., alias="remindSubscribers")
|
133
|
+
notify_subscribers_on_start: Optional[bool] = Field(..., alias="notifySubscribersOnStart")
|
134
|
+
notify_subscribers_on_completion: Optional[bool] = Field(..., alias="notifySubscribersOnCompletion")
|
122
135
|
|
123
136
|
|
124
137
|
class MaintenanceV1(ConfiguredBaseModel):
|
@@ -126,6 +139,7 @@ class MaintenanceV1(ConfiguredBaseModel):
|
|
126
139
|
message: str = Field(..., alias="message")
|
127
140
|
scheduled_start: str = Field(..., alias="scheduledStart")
|
128
141
|
scheduled_end: str = Field(..., alias="scheduledEnd")
|
142
|
+
affected_services: list[MaintenanceV1_AppV1] = Field(..., alias="affectedServices")
|
129
143
|
announcements: Optional[list[Union[MaintenanceStatuspageAnnouncementV1, MaintenanceAnnouncementV1]]] = Field(..., alias="announcements")
|
130
144
|
|
131
145
|
|
@@ -15,6 +15,7 @@ from reconcile.gql_definitions.statuspage.statuspages import StatusPageV1
|
|
15
15
|
from reconcile.statuspage.page import (
|
16
16
|
StatusComponent,
|
17
17
|
StatusMaintenance,
|
18
|
+
StatusMaintenanceAnnouncement,
|
18
19
|
StatusPage,
|
19
20
|
)
|
20
21
|
from reconcile.statuspage.state import ComponentBindingState
|
@@ -54,6 +55,10 @@ class AtlassianRawMaintenance(BaseModel):
|
|
54
55
|
scheduled_for: str
|
55
56
|
scheduled_until: str
|
56
57
|
incident_updates: list[AtlassianRawMaintenanceUpdate]
|
58
|
+
components: list[AtlassianRawComponent]
|
59
|
+
auto_transition_deliver_notifications_at_end: bool
|
60
|
+
auto_transition_deliver_notifications_at_start: bool
|
61
|
+
scheduled_remind_prior: bool
|
57
62
|
|
58
63
|
|
59
64
|
class AtlassianAPI:
|
@@ -121,6 +126,11 @@ class AtlassianAPI:
|
|
121
126
|
all_scheduled_incidents = self._list_items(url)
|
122
127
|
return [AtlassianRawMaintenance(**i) for i in all_scheduled_incidents]
|
123
128
|
|
129
|
+
def list_active_maintenances(self) -> list[AtlassianRawMaintenance]:
|
130
|
+
url = f"{self.api_url}/v1/pages/{self.page_id}/incidents/active_maintenance"
|
131
|
+
all_active_incidents = self._list_items(url)
|
132
|
+
return [AtlassianRawMaintenance(**i) for i in all_active_incidents]
|
133
|
+
|
124
134
|
def create_incident(self, data: dict[str, Any]) -> str:
|
125
135
|
url = f"{self.api_url}/v1/pages/{self.page_id}/incidents"
|
126
136
|
response = requests.post(
|
@@ -190,6 +200,26 @@ class AtlassianStatusPageProvider:
|
|
190
200
|
components=[c for c in components if c is not None],
|
191
201
|
)
|
192
202
|
|
203
|
+
def _raw_component_to_status_component(
|
204
|
+
self, raw_component: AtlassianRawComponent, name_override: Optional[str] = None
|
205
|
+
) -> StatusComponent:
|
206
|
+
group_name = (
|
207
|
+
self._group_id_to_name.get(raw_component.group_id)
|
208
|
+
if raw_component.group_id
|
209
|
+
else None
|
210
|
+
)
|
211
|
+
return StatusComponent(
|
212
|
+
name=name_override or raw_component.name,
|
213
|
+
display_name=raw_component.name,
|
214
|
+
description=raw_component.description,
|
215
|
+
group_name=group_name,
|
216
|
+
status_provider_configs=[
|
217
|
+
ManualStatusProvider(
|
218
|
+
component_status=raw_component.status,
|
219
|
+
)
|
220
|
+
],
|
221
|
+
)
|
222
|
+
|
193
223
|
def _bound_raw_component_to_status_component(
|
194
224
|
self, raw_component: AtlassianRawComponent
|
195
225
|
) -> Optional[StatusComponent]:
|
@@ -197,21 +227,8 @@ class AtlassianStatusPageProvider:
|
|
197
227
|
raw_component.id
|
198
228
|
)
|
199
229
|
if bound_component_name:
|
200
|
-
|
201
|
-
|
202
|
-
if raw_component.group_id
|
203
|
-
else None
|
204
|
-
)
|
205
|
-
return StatusComponent(
|
206
|
-
name=bound_component_name,
|
207
|
-
display_name=raw_component.name,
|
208
|
-
description=raw_component.description,
|
209
|
-
group_name=group_name,
|
210
|
-
status_provider_configs=[
|
211
|
-
ManualStatusProvider(
|
212
|
-
component_status=raw_component.status,
|
213
|
-
)
|
214
|
-
],
|
230
|
+
return self._raw_component_to_status_component(
|
231
|
+
raw_component, name_override=bound_component_name
|
215
232
|
)
|
216
233
|
return None
|
217
234
|
|
@@ -332,7 +349,13 @@ class AtlassianStatusPageProvider:
|
|
332
349
|
# resolve status
|
333
350
|
desired_component_status = desired.desired_component_status()
|
334
351
|
if desired_component_status:
|
335
|
-
|
352
|
+
active_maintenance_affecting_component = [
|
353
|
+
m
|
354
|
+
for m in self.active_maintenances
|
355
|
+
if desired.display_name in [c.name for c in m.components]
|
356
|
+
]
|
357
|
+
if not active_maintenance_affecting_component:
|
358
|
+
component_update["status"] = desired_component_status
|
336
359
|
|
337
360
|
if current_component:
|
338
361
|
logging.info(f"update component {desired.name}: {component_update}")
|
@@ -377,6 +400,27 @@ class AtlassianStatusPageProvider:
|
|
377
400
|
if not dry_run:
|
378
401
|
self._binding_state.bind_component(component_name, component_id)
|
379
402
|
|
403
|
+
def _raw_maintenance_to_status_maintenance(
|
404
|
+
self,
|
405
|
+
raw_maintenance: AtlassianRawMaintenance,
|
406
|
+
name_override: Optional[str] = None,
|
407
|
+
) -> StatusMaintenance:
|
408
|
+
return StatusMaintenance(
|
409
|
+
name=name_override or raw_maintenance.name,
|
410
|
+
message=raw_maintenance.incident_updates[0].body,
|
411
|
+
schedule_start=raw_maintenance.scheduled_for,
|
412
|
+
schedule_end=raw_maintenance.scheduled_until,
|
413
|
+
components=[
|
414
|
+
self._raw_component_to_status_component(c)
|
415
|
+
for c in raw_maintenance.components
|
416
|
+
],
|
417
|
+
announcements=StatusMaintenanceAnnouncement(
|
418
|
+
remind_subscribers=raw_maintenance.scheduled_remind_prior,
|
419
|
+
notify_subscribers_on_start=raw_maintenance.auto_transition_deliver_notifications_at_start,
|
420
|
+
notify_subscribers_on_completion=raw_maintenance.auto_transition_deliver_notifications_at_end,
|
421
|
+
),
|
422
|
+
)
|
423
|
+
|
380
424
|
@classmethod
|
381
425
|
def init_from_page(
|
382
426
|
cls,
|
@@ -398,17 +442,19 @@ class AtlassianStatusPageProvider:
|
|
398
442
|
)
|
399
443
|
|
400
444
|
@property
|
401
|
-
def
|
445
|
+
def scheduled_maintenances(self) -> list[StatusMaintenance]:
|
402
446
|
return [
|
403
|
-
|
404
|
-
name=m.name,
|
405
|
-
message=m.incident_updates[0].body,
|
406
|
-
schedule_start=m.scheduled_for,
|
407
|
-
schedule_end=m.scheduled_until,
|
408
|
-
)
|
447
|
+
self._raw_maintenance_to_status_maintenance(m)
|
409
448
|
for m in self._api.list_scheduled_maintenances()
|
410
449
|
]
|
411
450
|
|
451
|
+
@property
|
452
|
+
def active_maintenances(self) -> list[StatusMaintenance]:
|
453
|
+
return [
|
454
|
+
self._raw_maintenance_to_status_maintenance(m)
|
455
|
+
for m in self._api.list_active_maintenances()
|
456
|
+
]
|
457
|
+
|
412
458
|
def create_maintenance(self, maintenance: StatusMaintenance) -> None:
|
413
459
|
data = {
|
414
460
|
"name": maintenance.name,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import sys
|
3
|
+
from datetime import datetime, timezone
|
3
4
|
|
4
5
|
from reconcile.statuspage.atlassian import AtlassianStatusPageProvider
|
5
6
|
from reconcile.statuspage.integration import get_binding_state, get_status_pages
|
@@ -46,23 +47,32 @@ class StatusPageMaintenancesIntegration(QontractReconcileIntegration[NoParams]):
|
|
46
47
|
def run(self, dry_run: bool = False) -> None:
|
47
48
|
binding_state = get_binding_state(self.name, self.secret_reader)
|
48
49
|
pages = get_status_pages()
|
50
|
+
now = datetime.now(timezone.utc)
|
49
51
|
|
50
52
|
error = False
|
51
53
|
for p in pages:
|
52
54
|
try:
|
53
55
|
desired_state = [
|
54
|
-
StatusMaintenance.init_from_maintenance(
|
56
|
+
StatusMaintenance.init_from_maintenance(
|
57
|
+
m, page_components=p.components or []
|
58
|
+
)
|
55
59
|
for m in p.maintenances or []
|
60
|
+
if datetime.fromisoformat(m.scheduled_start) > now
|
56
61
|
]
|
57
62
|
page_provider = AtlassianStatusPageProvider.init_from_page(
|
58
63
|
page=p,
|
59
64
|
token=self.secret_reader.read_secret(p.credentials),
|
60
65
|
component_binding_state=binding_state,
|
61
66
|
)
|
67
|
+
current_state = [
|
68
|
+
m
|
69
|
+
for m in page_provider.scheduled_maintenances
|
70
|
+
if page_provider.has_component_binding_for(m.name)
|
71
|
+
]
|
62
72
|
self.reconcile(
|
63
73
|
dry_run=dry_run,
|
64
74
|
desired_state=desired_state,
|
65
|
-
current_state=
|
75
|
+
current_state=current_state,
|
66
76
|
provider=page_provider,
|
67
77
|
)
|
68
78
|
except Exception:
|
reconcile/statuspage/page.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
from typing import Optional, Self
|
1
|
+
from typing import Optional, Self, cast
|
2
2
|
|
3
3
|
from pydantic import BaseModel
|
4
4
|
|
5
|
+
from reconcile.gql_definitions.maintenance.maintenances import (
|
6
|
+
MaintenanceStatuspageAnnouncementV1,
|
7
|
+
)
|
5
8
|
from reconcile.gql_definitions.statuspage.statuspages import (
|
6
9
|
MaintenanceV1,
|
7
10
|
StatusPageComponentV1,
|
@@ -12,6 +15,8 @@ from reconcile.statuspage.status import (
|
|
12
15
|
build_status_provider_config,
|
13
16
|
)
|
14
17
|
|
18
|
+
PROVIDER_NAME = "statuspage"
|
19
|
+
|
15
20
|
|
16
21
|
class StatusComponent(BaseModel):
|
17
22
|
"""
|
@@ -47,12 +52,14 @@ class StatusComponent(BaseModel):
|
|
47
52
|
arbitrary_types_allowed = True
|
48
53
|
|
49
54
|
@classmethod
|
50
|
-
def init_from_page_component(
|
55
|
+
def init_from_page_component(
|
56
|
+
cls, component: StatusPageComponentV1, name_override: Optional[str] = None
|
57
|
+
) -> Self:
|
51
58
|
status_configs = [
|
52
59
|
build_status_provider_config(cfg) for cfg in component.status_config or []
|
53
60
|
]
|
54
61
|
return cls(
|
55
|
-
name=component.name,
|
62
|
+
name=name_override or component.name,
|
56
63
|
display_name=component.display_name,
|
57
64
|
description=component.description,
|
58
65
|
group_name=component.group_name,
|
@@ -94,6 +101,26 @@ class StatusPage(BaseModel):
|
|
94
101
|
)
|
95
102
|
|
96
103
|
|
104
|
+
class StatusMaintenanceAnnouncement(BaseModel):
|
105
|
+
"""
|
106
|
+
Represents the desired state of a status maintenance.
|
107
|
+
"""
|
108
|
+
|
109
|
+
remind_subscribers: Optional[bool] = None
|
110
|
+
notify_subscribers_on_start: Optional[bool] = None
|
111
|
+
notify_subscribers_on_completion: Optional[bool] = None
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def init_from_announcement(
|
115
|
+
cls, announcement: MaintenanceStatuspageAnnouncementV1
|
116
|
+
) -> Self:
|
117
|
+
return cls(
|
118
|
+
remind_subscribers=announcement.remind_subscribers,
|
119
|
+
notify_subscribers_on_start=announcement.notify_subscribers_on_start,
|
120
|
+
notify_subscribers_on_completion=announcement.notify_subscribers_on_completion,
|
121
|
+
)
|
122
|
+
|
123
|
+
|
97
124
|
class StatusMaintenance(BaseModel):
|
98
125
|
"""
|
99
126
|
Represents the desired state of a status maintenance.
|
@@ -103,12 +130,37 @@ class StatusMaintenance(BaseModel):
|
|
103
130
|
message: str
|
104
131
|
schedule_start: str
|
105
132
|
schedule_end: str
|
133
|
+
components: list[StatusComponent]
|
134
|
+
announcements: StatusMaintenanceAnnouncement
|
106
135
|
|
107
136
|
@classmethod
|
108
|
-
def init_from_maintenance(
|
137
|
+
def init_from_maintenance(
|
138
|
+
cls,
|
139
|
+
maintenance: MaintenanceV1,
|
140
|
+
page_components: list[StatusPageComponentV1],
|
141
|
+
) -> Self:
|
142
|
+
affected_services = [a.name for a in maintenance.affected_services]
|
143
|
+
affected_components = [
|
144
|
+
StatusComponent.init_from_page_component(c, name_override=c.display_name)
|
145
|
+
for c in page_components
|
146
|
+
if c.app.name in affected_services
|
147
|
+
]
|
148
|
+
statuspage_announcements = [
|
149
|
+
StatusMaintenanceAnnouncement.init_from_announcement(
|
150
|
+
cast(MaintenanceStatuspageAnnouncementV1, m)
|
151
|
+
)
|
152
|
+
for m in maintenance.announcements or []
|
153
|
+
if m.provider == PROVIDER_NAME
|
154
|
+
]
|
155
|
+
if len(statuspage_announcements) != 1:
|
156
|
+
raise ValueError(
|
157
|
+
f"Maintenanace announcements must include exactly one item of provider {PROVIDER_NAME}"
|
158
|
+
)
|
109
159
|
return cls(
|
110
160
|
name=maintenance.name,
|
111
161
|
message=maintenance.message.rstrip("\n"),
|
112
162
|
schedule_start=maintenance.scheduled_start,
|
113
163
|
schedule_end=maintenance.scheduled_end,
|
164
|
+
components=affected_components,
|
165
|
+
announcements=statuspage_announcements[0],
|
114
166
|
)
|
reconcile/utils/secret_reader.py
CHANGED
@@ -12,7 +12,6 @@ from typing import (
|
|
12
12
|
)
|
13
13
|
|
14
14
|
from hvac.exceptions import Forbidden
|
15
|
-
from sretoolbox.utils import retry
|
16
15
|
|
17
16
|
from reconcile.utils import (
|
18
17
|
config,
|
@@ -152,7 +151,6 @@ class VaultSecretReader(SecretReaderBase):
|
|
152
151
|
self._vault_client = VaultClient()
|
153
152
|
return self._vault_client
|
154
153
|
|
155
|
-
@retry()
|
156
154
|
def _read_all(
|
157
155
|
self, path: str, field: str, format: Optional[str], version: Optional[int]
|
158
156
|
) -> dict[str, str]:
|
@@ -173,10 +171,9 @@ class VaultSecretReader(SecretReaderBase):
|
|
173
171
|
raise SecretNotFound(*e.args) from e
|
174
172
|
return data
|
175
173
|
|
176
|
-
@retry()
|
177
174
|
def _read(
|
178
175
|
self, path: str, field: str, format: Optional[str], version: Optional[int]
|
179
|
-
) ->
|
176
|
+
) -> str:
|
180
177
|
try:
|
181
178
|
data = self.vault_client.read( # type: ignore[attr-defined] # mypy doesn't recognize the VaultClient.__new__ method
|
182
179
|
self._parameters_to_dict(
|
@@ -259,7 +256,6 @@ class SecretReader(SecretReaderBase):
|
|
259
256
|
self._vault_client = VaultClient()
|
260
257
|
return self._vault_client
|
261
258
|
|
262
|
-
@retry()
|
263
259
|
def _read(
|
264
260
|
self, path: str, field: str, format: Optional[str], version: Optional[int]
|
265
261
|
) -> str:
|
@@ -295,7 +291,6 @@ class SecretReader(SecretReaderBase):
|
|
295
291
|
|
296
292
|
return data
|
297
293
|
|
298
|
-
@retry()
|
299
294
|
def _read_all(
|
300
295
|
self, path: str, field: str, format: Optional[str], version: Optional[int]
|
301
296
|
) -> dict[str, str]:
|
reconcile/utils/vault.py
CHANGED
tools/qontract_cli.py
CHANGED
@@ -2597,7 +2597,7 @@ def maintenances(ctx):
|
|
2597
2597
|
"services": ", ".join(a.name for a in m.affected_services),
|
2598
2598
|
}
|
2599
2599
|
for m in maintenances
|
2600
|
-
if datetime.fromisoformat(m.
|
2600
|
+
if datetime.fromisoformat(m.scheduled_start) > now
|
2601
2601
|
]
|
2602
2602
|
columns = [
|
2603
2603
|
"name",
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc813.dist-info → qontract_reconcile-0.10.1rc815.dist-info}/top_level.txt
RENAMED
File without changes
|