qontract-reconcile 0.10.1rc706__py3-none-any.whl → 0.10.1rc708__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.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/RECORD +13 -6
- reconcile/cli.py +11 -0
- reconcile/deadmanssnitch.py +208 -0
- reconcile/gql_definitions/common/app_interface_dms_settings.py +86 -0
- reconcile/gql_definitions/common/clusters_with_dms.py +72 -0
- reconcile/test/test_deadmanssnitch.py +274 -0
- reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +19 -0
- reconcile/typed_queries/clusters_with_dms.py +18 -0
- reconcile/utils/deadmanssnitch_api.py +85 -0
- {qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.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.1rc708
|
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.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/RECORD
RENAMED
@@ -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=
|
12
|
+
reconcile/cli.py,sha256=Xci_L0O3nPUPK3HA1NhIhbrzcPLZkXT65fCaJA66Tvg,96354
|
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
|
@@ -18,6 +18,7 @@ reconcile/dashdotdb_dora.py,sha256=n9EJXhxCoMYuldj4Fa5s0TqfiiolSrqDEOCaLBV3uag,1
|
|
18
18
|
reconcile/dashdotdb_dvo.py,sha256=YXqpI6fBQAql-ybGI0grj9gWMzmKiAvPE__pNju6obk,8996
|
19
19
|
reconcile/dashdotdb_slo.py,sha256=bf1WSh5JP9obHVQsMy0OO71_VTYZgwAopElFZM6DmRo,6714
|
20
20
|
reconcile/database_access_manager.py,sha256=42dBJyihdwx4WjEBjwi3lUiDzQ1t_2ZFViJri2c4_aE,25716
|
21
|
+
reconcile/deadmanssnitch.py,sha256=ET4gUX1WvmWsILI10GgcPDOuJL2f3bDwwvKORWvyFhc,7339
|
21
22
|
reconcile/dynatrace_token_provider.py,sha256=P5jvMavremWp64LVknz1kCZI4aagwLrDDfXkmJ9diwY,17212
|
22
23
|
reconcile/email_sender.py,sha256=-5L-Ag_jaEYSzYRoMr52KQBRXz1E8yx9GqLbg2X4XFU,3533
|
23
24
|
reconcile/gabi_authorized_users.py,sha256=9kpSJGyMe_qYVHIgTFHhYf8E3lKSLO0Ia1WwK9ADNIE,4502
|
@@ -215,12 +216,14 @@ reconcile/gql_definitions/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
215
216
|
reconcile/gql_definitions/common/alerting_services_settings.py,sha256=mT7cobC9mR_bhFSYeQX1apVA5zMqSbu5fYcdd4iZ9mg,1802
|
216
217
|
reconcile/gql_definitions/common/app_code_component_repos.py,sha256=eGlv8SRiLl-QUYNDl_gAvtQGMB2Hr31EqUXYQO_wOcg,1835
|
217
218
|
reconcile/gql_definitions/common/app_interface_custom_messages.py,sha256=5a-rmQqR0qmews01K1WiTw1ZHUIUmQoznF9v8_Uw9QQ,2000
|
219
|
+
reconcile/gql_definitions/common/app_interface_dms_settings.py,sha256=h-N7-XGpmH7O9d3UBPhJJl48tpZvqihLloamZKdlgVg,2568
|
218
220
|
reconcile/gql_definitions/common/app_interface_repo_settings.py,sha256=rud0rz9NIFF-h1fFdk3MnwGmx73rhwrn1taN_HefvyU,1754
|
219
221
|
reconcile/gql_definitions/common/app_interface_state_settings.py,sha256=VXIK0Hmyv6GTShI86IGkjxyHGwufqUBAh617XKUAKaI,2507
|
220
222
|
reconcile/gql_definitions/common/app_interface_vault_settings.py,sha256=w8quvdG0cSq71ZyJokPPp7MyMpoDb6-HLQ3o9JHVGRQ,1771
|
221
223
|
reconcile/gql_definitions/common/aws_vpcs.py,sha256=Dss9dQ3xagnz3Ltg1e9mtG2PAmQGBbUzKCmmzvuN28s,1892
|
222
224
|
reconcile/gql_definitions/common/clusters.py,sha256=eJIbMQltj-Sc7h_QVIeFkbRy1b_QJIkHATfbagxM-yU,21527
|
223
225
|
reconcile/gql_definitions/common/clusters_minimal.py,sha256=yZpjS9qWyusCEiWtD8wzf0tak298uQyxiN4lC6KNo4s,4475
|
226
|
+
reconcile/gql_definitions/common/clusters_with_dms.py,sha256=GJ53P8tgMLh1NfVkaV9_AmaqF9pNUqJZcDkcKzKzUy0,2242
|
224
227
|
reconcile/gql_definitions/common/clusters_with_peering.py,sha256=6mv1m8lc9UDzMWXatkTbYHwBaXoHIIC62qXULsfQN5Y,11822
|
225
228
|
reconcile/gql_definitions/common/github_orgs.py,sha256=rZ0pDAA2_9hF9N-ykRZIxPtEmczTSjuA_k3nkp0k1W0,2039
|
226
229
|
reconcile/gql_definitions/common/jira_settings.py,sha256=Fmjxhlhr69kc4jkG_0k17fuYlQVucbNex0jXYu83wbY,1990
|
@@ -436,6 +439,7 @@ reconcile/test/test_cli.py,sha256=qx_iBwh4Z-YkK3sbjK1wEziPTgn060EN-baf9DNvR3k,10
|
|
436
439
|
reconcile/test/test_closedbox_endpoint_monitoring.py,sha256=isMHYwRWMFARU2nbJgbl69kD6H0eA86noCM4MPVI1fo,7151
|
437
440
|
reconcile/test/test_dashdotdb_dora.py,sha256=MfHGAsX2eSQSvBVt9_1Sah3aQKNJBXA9Iu86X0NWD6c,7705
|
438
441
|
reconcile/test/test_database_access_manager.py,sha256=-9fYo8wMNhbJUTK_bd7g_fS5zYsAlqQ0rBDDYBMZvZQ,19595
|
442
|
+
reconcile/test/test_deadmanssnitch.py,sha256=3AiarCKkSPU1cEkVwooglwU3sQgDifMAiXuJb1P9_vI,9220
|
439
443
|
reconcile/test/test_gabi_authorized_users.py,sha256=6XnV5Q9inxP81ktGMVKyWucjBTUj8Imy2L0HG3YHyUE,2496
|
440
444
|
reconcile/test/test_gcr_mirror.py,sha256=A0y8auKZzr62-mGoxSQ__JnN0-ijZUltzjwR5miBgso,490
|
441
445
|
reconcile/test/test_github_org.py,sha256=j3KeB4OnSln1gm2hidce49xdMru-j75NS3cM-AEgzZc,4511
|
@@ -534,12 +538,14 @@ reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas
|
|
534
538
|
reconcile/typed_queries/__init__.py,sha256=rRk4CyslLsBr4vAh1pIPgt6s3P4R1M9NSEPLnyQgBpk,61
|
535
539
|
reconcile/typed_queries/alerting_services_settings.py,sha256=sX6s8GY-BB0UHogMC1ICeREVab-IYrNm1c-hqMGEdYQ,864
|
536
540
|
reconcile/typed_queries/app_interface_custom_messages.py,sha256=5HWr68_kb4bEL8pDCIH0ez6GOrdwdbGF6w88xV0_Ccs,718
|
541
|
+
reconcile/typed_queries/app_interface_deadmanssnitch_settings.py,sha256=y30_SmS0JkxUN1qbpNqYnl8gEUBmom8UIEH4i9_Pl2E,683
|
537
542
|
reconcile/typed_queries/app_interface_repo_url.py,sha256=ePr_nvjDeyZPR4sHsDQFubV2gslj9lPUSzIWWhm0syo,609
|
538
543
|
reconcile/typed_queries/app_interface_state_settings.py,sha256=ytNAf3feg8JwmdCC4vO1WtVb-Ok5_ZtNAL3mXe2d1Pw,479
|
539
544
|
reconcile/typed_queries/app_interface_vault_settings.py,sha256=uFS7qLjjGnrK8gm2fkUT8RFd7s_5kxTWWpxZRhr1Ndo,746
|
540
545
|
reconcile/typed_queries/aws_vpcs.py,sha256=bM7dWpVHV6J1WYjgHOe2rfAl4vKpUoC8khPQSdtkxVQ,371
|
541
546
|
reconcile/typed_queries/clusters.py,sha256=tptLP2Ov1lNqtDGuPKBOVvpDtREm5CQFDDVyp0xKkRQ,506
|
542
547
|
reconcile/typed_queries/clusters_minimal.py,sha256=flvs4oXVR4b971h-zMTK3C2GL3bjMnvZgrAeI3XgBQQ,517
|
548
|
+
reconcile/typed_queries/clusters_with_dms.py,sha256=hyNPaHBgUIhnAeuOSY5SG46U1zDp9KytUXEobkZdRlQ,551
|
543
549
|
reconcile/typed_queries/clusters_with_peering.py,sha256=lIai7SJJD0bqIJbe7virgrbYRqjLouSL2OpJD0itpAY,330
|
544
550
|
reconcile/typed_queries/get_state_aws_account.py,sha256=CSJjVPWsUZ2rkGIt8ehoQt7hokFqrUDgG9HFlg2lVD8,492
|
545
551
|
reconcile/typed_queries/github_orgs.py,sha256=UZhoPl8qvA_tcO7CZlN8GuMKckt3ywd47Suu61rgHsc,258
|
@@ -572,6 +578,7 @@ reconcile/utils/binary.py,sha256=EsOGg82Y2QJh91SGJE0tYpBKqU0iaaagQVoYONBtQn8,235
|
|
572
578
|
reconcile/utils/config.py,sha256=aId5zrPjM_84u_T4yTRE_Psu3zo5-5_JCR6_7Wgv5UQ,990
|
573
579
|
reconcile/utils/constants.py,sha256=pOUd97bqZdsAu5RWJ8NUs9cwCY7K9y0eW9VVeJ4fZIU,138
|
574
580
|
reconcile/utils/data_structures.py,sha256=VyKfnlNJTiRvZKNpfgIrjESQ2YgmEpWuPQXT14WA1vI,311
|
581
|
+
reconcile/utils/deadmanssnitch_api.py,sha256=hkfbfbRAhzLpI39o6Du7FZKVtf4UVJ1OljOQNUkmODM,2478
|
575
582
|
reconcile/utils/defer.py,sha256=SniUsbgOEs9Pa8JkecLu0F94O63yQPByKXaElDYe0FI,377
|
576
583
|
reconcile/utils/differ.py,sha256=kJmUp9ZffFPSUEviaAw3s9c92ErwRJeHaRexGPai7wA,7643
|
577
584
|
reconcile/utils/disabled_integrations.py,sha256=avdDsFyl_LdTsrPVzlcIhWzT_V4C4MXw1ZC__aOtluE,1126
|
@@ -754,8 +761,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
754
761
|
tools/test/test_qontract_cli.py,sha256=UEwAW7PA_GIrbqzaLxpkCxbuVjEFLNvnVG-6VyoCGIc,4147
|
755
762
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
756
763
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
757
|
-
qontract_reconcile-0.10.
|
758
|
-
qontract_reconcile-0.10.
|
759
|
-
qontract_reconcile-0.10.
|
760
|
-
qontract_reconcile-0.10.
|
761
|
-
qontract_reconcile-0.10.
|
764
|
+
qontract_reconcile-0.10.1rc708.dist-info/METADATA,sha256=jj1NGE9JNFWbhjQyp5dY0QHogw8JrqpK0_hKDkxtbUU,2382
|
765
|
+
qontract_reconcile-0.10.1rc708.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
766
|
+
qontract_reconcile-0.10.1rc708.dist-info/entry_points.txt,sha256=rIxI5zWtHNlfpDeq1a7pZXAPoqf7HG32KMTN3MeWK_8,429
|
767
|
+
qontract_reconcile-0.10.1rc708.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
768
|
+
qontract_reconcile-0.10.1rc708.dist-info/RECORD,,
|
reconcile/cli.py
CHANGED
@@ -3431,6 +3431,17 @@ def acs_policies(ctx):
|
|
3431
3431
|
)
|
3432
3432
|
|
3433
3433
|
|
3434
|
+
@integration.command(short_help="Automate Deadmanssnitch Creation/Deletion")
|
3435
|
+
@click.pass_context
|
3436
|
+
def deadmanssnitch(ctx):
|
3437
|
+
from reconcile import deadmanssnitch
|
3438
|
+
|
3439
|
+
run_class_integration(
|
3440
|
+
integration=deadmanssnitch.DeadMansSnitchIntegration(),
|
3441
|
+
ctx=ctx.obj,
|
3442
|
+
)
|
3443
|
+
|
3444
|
+
|
3434
3445
|
def get_integration_cli_meta() -> dict[str, IntegrationMeta]:
|
3435
3446
|
"""
|
3436
3447
|
returns all integrations known to cli.py via click introspection
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import (
|
3
|
+
Optional,
|
4
|
+
cast,
|
5
|
+
)
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from reconcile.gql_definitions.common.clusters_with_dms import ClusterV1
|
10
|
+
from reconcile.typed_queries.app_interface_deadmanssnitch_settings import (
|
11
|
+
get_deadmanssnitch_settings,
|
12
|
+
)
|
13
|
+
from reconcile.typed_queries.clusters_with_dms import get_clusters_with_dms
|
14
|
+
from reconcile.utils.deadmanssnitch_api import (
|
15
|
+
DeadMansSnitchApi,
|
16
|
+
Snitch,
|
17
|
+
)
|
18
|
+
from reconcile.utils.differ import diff_mappings
|
19
|
+
from reconcile.utils.runtime.integration import (
|
20
|
+
NoParams,
|
21
|
+
QontractReconcileIntegration,
|
22
|
+
)
|
23
|
+
from reconcile.utils.secret_reader import (
|
24
|
+
SecretNotFound,
|
25
|
+
)
|
26
|
+
from reconcile.utils.semver_helper import make_semver
|
27
|
+
from reconcile.utils.vault import (
|
28
|
+
SecretFieldNotFound,
|
29
|
+
VaultClient,
|
30
|
+
_VaultClient,
|
31
|
+
)
|
32
|
+
|
33
|
+
QONTRACT_INTEGRATION = "deadmanssnitch"
|
34
|
+
SECRET_NOT_FOUND = "SECRET_NOT_FOUND"
|
35
|
+
|
36
|
+
|
37
|
+
class SnitchSpec(BaseModel):
|
38
|
+
"""Class to hold values from cluster file and settings"""
|
39
|
+
|
40
|
+
name: str
|
41
|
+
alert_email: list[str]
|
42
|
+
alert_type: str
|
43
|
+
interval: str
|
44
|
+
tags: list[str]
|
45
|
+
notes: str
|
46
|
+
|
47
|
+
|
48
|
+
class DeadMansSnitchIntegration(QontractReconcileIntegration[NoParams]):
|
49
|
+
"""Integration to automate deadmanssnitch snitch api during cluster dressup."""
|
50
|
+
|
51
|
+
@property
|
52
|
+
def name(self) -> str:
|
53
|
+
return QONTRACT_INTEGRATION
|
54
|
+
|
55
|
+
def __init__(self) -> None:
|
56
|
+
super().__init__(NoParams())
|
57
|
+
self.qontract_integration_version = make_semver(0, 1, 0)
|
58
|
+
self.settings = get_deadmanssnitch_settings()
|
59
|
+
self.vault_client = cast(_VaultClient, VaultClient())
|
60
|
+
|
61
|
+
@staticmethod
|
62
|
+
def get_snitch_name(cluster: ClusterV1) -> str:
|
63
|
+
return cluster.prometheus_url.replace("https://", "")
|
64
|
+
|
65
|
+
def write_snitch_to_vault(
|
66
|
+
self, cluster_name: str, snitch_url: Optional[str]
|
67
|
+
) -> None:
|
68
|
+
if snitch_url:
|
69
|
+
self.vault_client.write(
|
70
|
+
{
|
71
|
+
"path": self.settings.snitches_path,
|
72
|
+
"data": {f"deadmanssnitch-{cluster_name}-url": snitch_url},
|
73
|
+
},
|
74
|
+
decode_base64=False,
|
75
|
+
)
|
76
|
+
|
77
|
+
def add_vault_data(
|
78
|
+
self, cluster_name: str, snitch: Snitch, snitch_secret_path: str
|
79
|
+
) -> Snitch:
|
80
|
+
try:
|
81
|
+
full_secret_path = {
|
82
|
+
"path": snitch_secret_path,
|
83
|
+
"field": f"deadmanssnitch-{cluster_name}-url",
|
84
|
+
}
|
85
|
+
snitch.vault_data = self.secret_reader.read(full_secret_path).strip()
|
86
|
+
except (SecretNotFound, SecretFieldNotFound):
|
87
|
+
snitch.vault_data = SECRET_NOT_FOUND
|
88
|
+
return snitch
|
89
|
+
|
90
|
+
def create_snitch(
|
91
|
+
self,
|
92
|
+
cluster_name: str,
|
93
|
+
snitch_spec: SnitchSpec,
|
94
|
+
deadmanssnitch_api: DeadMansSnitchApi,
|
95
|
+
) -> None:
|
96
|
+
payload = {
|
97
|
+
"name": snitch_spec.name,
|
98
|
+
"alert_type": snitch_spec.alert_type,
|
99
|
+
"interval": snitch_spec.interval,
|
100
|
+
"tags": snitch_spec.tags,
|
101
|
+
"alert_email": snitch_spec.alert_email,
|
102
|
+
"notes": snitch_spec.notes,
|
103
|
+
}
|
104
|
+
snitch_data = deadmanssnitch_api.create_snitch(payload=payload)
|
105
|
+
self.write_snitch_to_vault(
|
106
|
+
cluster_name=cluster_name, snitch_url=snitch_data.check_in_url
|
107
|
+
)
|
108
|
+
|
109
|
+
def reconcile(
|
110
|
+
self,
|
111
|
+
dry_run: bool,
|
112
|
+
current_state: dict[str, Snitch],
|
113
|
+
desired_state: dict[str, SnitchSpec],
|
114
|
+
deadmanssnitch_api: DeadMansSnitchApi,
|
115
|
+
) -> None:
|
116
|
+
diffs = diff_mappings(
|
117
|
+
current=current_state,
|
118
|
+
desired=desired_state,
|
119
|
+
equal=lambda current, desired: current.name == desired.name,
|
120
|
+
)
|
121
|
+
errors = []
|
122
|
+
for cluster_name, snitch in diffs.add.items():
|
123
|
+
logging.info("[cluster_name:%s] [Action:create_snitch]", cluster_name)
|
124
|
+
if not dry_run:
|
125
|
+
try:
|
126
|
+
self.create_snitch(cluster_name, snitch, deadmanssnitch_api)
|
127
|
+
except Exception as e:
|
128
|
+
errors.append(e)
|
129
|
+
for cluster_name, snitch_value in diffs.delete.items():
|
130
|
+
logging.info("[cluster_name:%s] [Action:delete_snitch]", cluster_name)
|
131
|
+
if not dry_run:
|
132
|
+
try:
|
133
|
+
deadmanssnitch_api.delete_snitch(snitch_value.token)
|
134
|
+
except Exception as e:
|
135
|
+
errors.append(e)
|
136
|
+
for cluster_name, diff_pair in diffs.identical.items():
|
137
|
+
if diff_pair.current.needs_vault_update():
|
138
|
+
logging.info("[cluster_name:%s] [Action:update_vault]", cluster_name)
|
139
|
+
if not dry_run:
|
140
|
+
try:
|
141
|
+
self.write_snitch_to_vault(
|
142
|
+
cluster_name=cluster_name,
|
143
|
+
snitch_url=diff_pair.current.check_in_url,
|
144
|
+
)
|
145
|
+
except Exception as e:
|
146
|
+
errors.append(e)
|
147
|
+
if errors:
|
148
|
+
raise ExceptionGroup("Errors occurred while reconcile", errors)
|
149
|
+
|
150
|
+
def get_current_state(
|
151
|
+
self,
|
152
|
+
deadmanssnitch_api: DeadMansSnitchApi,
|
153
|
+
clusters: list[ClusterV1],
|
154
|
+
) -> dict[str, Snitch]:
|
155
|
+
snitch_name_to_cluster_name_mapping = {
|
156
|
+
self.get_snitch_name(cluster): cluster.name for cluster in clusters
|
157
|
+
}
|
158
|
+
# current state includes for deadmanssnithch response and associated secret in vault
|
159
|
+
snitches = deadmanssnitch_api.get_snitches(tags=self.settings.tags)
|
160
|
+
# create snitch_map only for the desired clusters
|
161
|
+
current_state = {
|
162
|
+
cluster_name: self.add_vault_data(
|
163
|
+
cluster_name, snitch, self.settings.snitches_path
|
164
|
+
)
|
165
|
+
for snitch in snitches
|
166
|
+
if (cluster_name := snitch_name_to_cluster_name_mapping.get(snitch.name))
|
167
|
+
}
|
168
|
+
return current_state
|
169
|
+
|
170
|
+
def get_desired_state(
|
171
|
+
self,
|
172
|
+
clusters: list[ClusterV1],
|
173
|
+
) -> dict[str, SnitchSpec]:
|
174
|
+
desired_state = {
|
175
|
+
cluster.name: SnitchSpec(
|
176
|
+
name=self.get_snitch_name(cluster),
|
177
|
+
alert_email=self.settings.alert_mail_addresses,
|
178
|
+
interval=self.settings.interval,
|
179
|
+
tags=self.settings.tags,
|
180
|
+
notes=self.settings.notes_link,
|
181
|
+
alert_type=self.settings.alert_type,
|
182
|
+
)
|
183
|
+
for cluster in clusters
|
184
|
+
if cluster.enable_dead_mans_snitch
|
185
|
+
}
|
186
|
+
return desired_state
|
187
|
+
|
188
|
+
def run(self, dry_run: bool) -> None:
|
189
|
+
# Initialize deadmanssnitch_api
|
190
|
+
token = self.secret_reader.read({
|
191
|
+
"path": self.settings.token_creds.path,
|
192
|
+
"field": self.settings.token_creds.field,
|
193
|
+
})
|
194
|
+
with DeadMansSnitchApi(token=token) as deadmanssnitch_api:
|
195
|
+
# desired state - get the clusters having enableDeadMansSnitch field
|
196
|
+
clusters = get_clusters_with_dms()
|
197
|
+
desired_state = self.get_desired_state(clusters)
|
198
|
+
# create current state from deadmanssnitch and vault
|
199
|
+
current_state = self.get_current_state(
|
200
|
+
deadmanssnitch_api,
|
201
|
+
clusters,
|
202
|
+
)
|
203
|
+
self.reconcile(
|
204
|
+
dry_run,
|
205
|
+
current_state=current_state,
|
206
|
+
desired_state=desired_state,
|
207
|
+
deadmanssnitch_api=deadmanssnitch_api,
|
208
|
+
)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
|
3
|
+
"""
|
4
|
+
from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
|
5
|
+
from datetime import datetime # noqa: F401 # pylint: disable=W0611
|
6
|
+
from enum import Enum # noqa: F401 # pylint: disable=W0611
|
7
|
+
from typing import ( # noqa: F401 # pylint: disable=W0611
|
8
|
+
Any,
|
9
|
+
Optional,
|
10
|
+
Union,
|
11
|
+
)
|
12
|
+
|
13
|
+
from pydantic import ( # noqa: F401 # pylint: disable=W0611
|
14
|
+
BaseModel,
|
15
|
+
Extra,
|
16
|
+
Field,
|
17
|
+
Json,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
DEFINITION = """
|
22
|
+
query DeadMansSnitchSettings {
|
23
|
+
settings: app_interface_settings_v1 {
|
24
|
+
deadMansSnitchSettings {
|
25
|
+
alertMailAddresses
|
26
|
+
notesLink
|
27
|
+
snitchesPath
|
28
|
+
tokenCreds {
|
29
|
+
path
|
30
|
+
field
|
31
|
+
}
|
32
|
+
tags
|
33
|
+
alertType
|
34
|
+
interval
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
"""
|
39
|
+
|
40
|
+
|
41
|
+
class ConfiguredBaseModel(BaseModel):
|
42
|
+
class Config:
|
43
|
+
smart_union=True
|
44
|
+
extra=Extra.forbid
|
45
|
+
|
46
|
+
|
47
|
+
class VaultSecretV1(ConfiguredBaseModel):
|
48
|
+
path: str = Field(..., alias="path")
|
49
|
+
field: str = Field(..., alias="field")
|
50
|
+
|
51
|
+
|
52
|
+
class DeadMansSnitchSettingsV1(ConfiguredBaseModel):
|
53
|
+
alert_mail_addresses: list[str] = Field(..., alias="alertMailAddresses")
|
54
|
+
notes_link: str = Field(..., alias="notesLink")
|
55
|
+
snitches_path: str = Field(..., alias="snitchesPath")
|
56
|
+
token_creds: VaultSecretV1 = Field(..., alias="tokenCreds")
|
57
|
+
tags: list[str] = Field(..., alias="tags")
|
58
|
+
alert_type: str = Field(..., alias="alertType")
|
59
|
+
interval: str = Field(..., alias="interval")
|
60
|
+
|
61
|
+
|
62
|
+
class AppInterfaceSettingsV1(ConfiguredBaseModel):
|
63
|
+
dead_mans_snitch_settings: Optional[DeadMansSnitchSettingsV1] = Field(..., alias="deadMansSnitchSettings")
|
64
|
+
|
65
|
+
|
66
|
+
class DeadMansSnitchSettingsQueryData(ConfiguredBaseModel):
|
67
|
+
settings: Optional[list[AppInterfaceSettingsV1]] = Field(..., alias="settings")
|
68
|
+
|
69
|
+
|
70
|
+
def query(query_func: Callable, **kwargs: Any) -> DeadMansSnitchSettingsQueryData:
|
71
|
+
"""
|
72
|
+
This is a convenience function which queries and parses the data into
|
73
|
+
concrete types. It should be compatible with most GQL clients.
|
74
|
+
You do not have to use it to consume the generated data classes.
|
75
|
+
Alternatively, you can also mime and alternate the behavior
|
76
|
+
of this function in the caller.
|
77
|
+
|
78
|
+
Parameters:
|
79
|
+
query_func (Callable): Function which queries your GQL Server
|
80
|
+
kwargs: optional arguments that will be passed to the query function
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
DeadMansSnitchSettingsQueryData: queried data parsed into generated classes
|
84
|
+
"""
|
85
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
86
|
+
return DeadMansSnitchSettingsQueryData(**raw_data)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""
|
2
|
+
Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
|
3
|
+
"""
|
4
|
+
from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
|
5
|
+
from datetime import datetime # noqa: F401 # pylint: disable=W0611
|
6
|
+
from enum import Enum # noqa: F401 # pylint: disable=W0611
|
7
|
+
from typing import ( # noqa: F401 # pylint: disable=W0611
|
8
|
+
Any,
|
9
|
+
Optional,
|
10
|
+
Union,
|
11
|
+
)
|
12
|
+
|
13
|
+
from pydantic import ( # noqa: F401 # pylint: disable=W0611
|
14
|
+
BaseModel,
|
15
|
+
Extra,
|
16
|
+
Field,
|
17
|
+
Json,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
DEFINITION = """
|
22
|
+
query ClustersWithMonitoring($filter: JSON){
|
23
|
+
clusters: clusters_v1(filter: $filter) {
|
24
|
+
name
|
25
|
+
serverUrl
|
26
|
+
consoleUrl
|
27
|
+
alertmanagerUrl
|
28
|
+
prometheusUrl
|
29
|
+
managedClusterRoles
|
30
|
+
enableDeadMansSnitch
|
31
|
+
}
|
32
|
+
}
|
33
|
+
"""
|
34
|
+
|
35
|
+
|
36
|
+
class ConfiguredBaseModel(BaseModel):
|
37
|
+
class Config:
|
38
|
+
smart_union=True
|
39
|
+
extra=Extra.forbid
|
40
|
+
|
41
|
+
|
42
|
+
class ClusterV1(ConfiguredBaseModel):
|
43
|
+
name: str = Field(..., alias="name")
|
44
|
+
server_url: str = Field(..., alias="serverUrl")
|
45
|
+
console_url: str = Field(..., alias="consoleUrl")
|
46
|
+
alertmanager_url: str = Field(..., alias="alertmanagerUrl")
|
47
|
+
prometheus_url: str = Field(..., alias="prometheusUrl")
|
48
|
+
managed_cluster_roles: Optional[bool] = Field(..., alias="managedClusterRoles")
|
49
|
+
enable_dead_mans_snitch: Optional[bool] = Field(..., alias="enableDeadMansSnitch")
|
50
|
+
|
51
|
+
|
52
|
+
class ClustersWithMonitoringQueryData(ConfiguredBaseModel):
|
53
|
+
clusters: Optional[list[ClusterV1]] = Field(..., alias="clusters")
|
54
|
+
|
55
|
+
|
56
|
+
def query(query_func: Callable, **kwargs: Any) -> ClustersWithMonitoringQueryData:
|
57
|
+
"""
|
58
|
+
This is a convenience function which queries and parses the data into
|
59
|
+
concrete types. It should be compatible with most GQL clients.
|
60
|
+
You do not have to use it to consume the generated data classes.
|
61
|
+
Alternatively, you can also mime and alternate the behavior
|
62
|
+
of this function in the caller.
|
63
|
+
|
64
|
+
Parameters:
|
65
|
+
query_func (Callable): Function which queries your GQL Server
|
66
|
+
kwargs: optional arguments that will be passed to the query function
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
ClustersWithMonitoringQueryData: queried data parsed into generated classes
|
70
|
+
"""
|
71
|
+
raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
|
72
|
+
return ClustersWithMonitoringQueryData(**raw_data)
|
@@ -0,0 +1,274 @@
|
|
1
|
+
from unittest.mock import MagicMock, create_autospec
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from pytest_mock import MockerFixture
|
5
|
+
|
6
|
+
from reconcile.deadmanssnitch import (
|
7
|
+
DeadMansSnitchIntegration,
|
8
|
+
)
|
9
|
+
from reconcile.gql_definitions.common.app_interface_dms_settings import (
|
10
|
+
DeadMansSnitchSettingsV1,
|
11
|
+
VaultSecretV1,
|
12
|
+
)
|
13
|
+
from reconcile.gql_definitions.common.clusters_with_dms import (
|
14
|
+
ClusterV1,
|
15
|
+
)
|
16
|
+
from reconcile.utils.deadmanssnitch_api import (
|
17
|
+
DeadMansSnitchApi,
|
18
|
+
Snitch,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
@pytest.fixture
|
23
|
+
def deadmanssnitch_api() -> MockerFixture:
|
24
|
+
return create_autospec(DeadMansSnitchApi)
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.fixture
|
28
|
+
def deadmanssnitch_settings() -> DeadMansSnitchSettingsV1:
|
29
|
+
settings = DeadMansSnitchSettingsV1(
|
30
|
+
alertMailAddresses=["test_email"],
|
31
|
+
notesLink="test_link",
|
32
|
+
snitchesPath="test_snitches_path",
|
33
|
+
tokenCreds=VaultSecretV1(path="test_path", field="test_field"),
|
34
|
+
tags=["test-tags"],
|
35
|
+
alertType="Heartbeat",
|
36
|
+
interval="15_minute",
|
37
|
+
)
|
38
|
+
return settings
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.fixture
|
42
|
+
def vault_mock(mocker: MockerFixture) -> MockerFixture:
|
43
|
+
return mocker.patch("reconcile.utils.vault._VaultClient")
|
44
|
+
|
45
|
+
|
46
|
+
@pytest.fixture
|
47
|
+
def secret_reader(mocker: MockerFixture) -> MockerFixture:
|
48
|
+
mock_secretreader = mocker.patch(
|
49
|
+
"reconcile.utils.secret_reader.SecretReader", autospec=True
|
50
|
+
)
|
51
|
+
mock_secretreader.read.return_value = "secret"
|
52
|
+
mock_secretreader.read_secret.return_value = "secret"
|
53
|
+
return mock_secretreader
|
54
|
+
|
55
|
+
|
56
|
+
def test_get_current_state(
|
57
|
+
secret_reader: MagicMock,
|
58
|
+
deadmanssnitch_api: MockerFixture,
|
59
|
+
mocker: MockerFixture,
|
60
|
+
deadmanssnitch_settings: DeadMansSnitchSettingsV1,
|
61
|
+
) -> None:
|
62
|
+
deadmanssnitch_api.get_snitches.return_value = [
|
63
|
+
Snitch(
|
64
|
+
name="prometheus.test_cluster_1.net",
|
65
|
+
token="test",
|
66
|
+
href="testc",
|
67
|
+
status="healthy",
|
68
|
+
alert_type="basic",
|
69
|
+
alert_email=["test_mail"],
|
70
|
+
interval="15_minute",
|
71
|
+
check_in_url="test_url",
|
72
|
+
tags=["app-sre"],
|
73
|
+
notes="test_notes",
|
74
|
+
)
|
75
|
+
]
|
76
|
+
clusters = [
|
77
|
+
ClusterV1(
|
78
|
+
name="test_cluster_1",
|
79
|
+
serverUrl="testurl",
|
80
|
+
consoleUrl="test_c_url",
|
81
|
+
alertmanagerUrl="test_alert_manager",
|
82
|
+
managedClusterRoles=True,
|
83
|
+
prometheusUrl="https://prometheus.test_cluster_1.net",
|
84
|
+
enableDeadMansSnitch=True,
|
85
|
+
),
|
86
|
+
ClusterV1(
|
87
|
+
name="test_cluster_2",
|
88
|
+
serverUrl="testurl",
|
89
|
+
consoleUrl="test_c_url",
|
90
|
+
alertmanagerUrl="test_alert_manager",
|
91
|
+
managedClusterRoles=True,
|
92
|
+
prometheusUrl="https://prometheus.test_cluster_2.net",
|
93
|
+
enableDeadMansSnitch=True,
|
94
|
+
),
|
95
|
+
]
|
96
|
+
mocker.patch(
|
97
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.__init__"
|
98
|
+
).return_value = None
|
99
|
+
dms_integration = DeadMansSnitchIntegration()
|
100
|
+
dms_integration._secret_reader = secret_reader
|
101
|
+
dms_integration.settings = deadmanssnitch_settings
|
102
|
+
current_state = dms_integration.get_current_state(
|
103
|
+
deadmanssnitch_api=deadmanssnitch_api,
|
104
|
+
clusters=clusters,
|
105
|
+
)
|
106
|
+
assert current_state["test_cluster_1"].vault_data == "secret"
|
107
|
+
|
108
|
+
|
109
|
+
def test_integration_for_create(
|
110
|
+
vault_mock: MagicMock,
|
111
|
+
deadmanssnitch_settings: DeadMansSnitchSettingsV1,
|
112
|
+
mocker: MockerFixture,
|
113
|
+
secret_reader: MagicMock,
|
114
|
+
) -> None:
|
115
|
+
mocker.patch(
|
116
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.__init__"
|
117
|
+
).return_value = None
|
118
|
+
dms_integration = DeadMansSnitchIntegration()
|
119
|
+
dms_integration._secret_reader = secret_reader
|
120
|
+
dms_integration.settings = deadmanssnitch_settings
|
121
|
+
dms_integration.vault_client = vault_mock
|
122
|
+
mocker.patch("reconcile.deadmanssnitch.get_clusters_with_dms").return_value = [
|
123
|
+
ClusterV1(
|
124
|
+
name="create_cluster",
|
125
|
+
prometheusUrl="https://prometheus.create_cluster.devshift.net",
|
126
|
+
enableDeadMansSnitch=True,
|
127
|
+
alertmanagerUrl="alertmanager.create_cluster.devshift.net",
|
128
|
+
managedClusterRoles=True,
|
129
|
+
serverUrl="testurl",
|
130
|
+
consoleUrl="test_console",
|
131
|
+
)
|
132
|
+
]
|
133
|
+
mocker.patch(
|
134
|
+
"reconcile.deadmanssnitch.DeadMansSnitchApi.get_snitches"
|
135
|
+
).return_value = []
|
136
|
+
mock_create_snitch = mocker.patch(
|
137
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.create_snitch"
|
138
|
+
)
|
139
|
+
mock_create_snitch.return_value = None
|
140
|
+
dms_integration.run(dry_run=False)
|
141
|
+
mock_create_snitch.assert_called_once()
|
142
|
+
|
143
|
+
|
144
|
+
def test_integration_for_delete(
|
145
|
+
deadmanssnitch_settings: DeadMansSnitchSettingsV1,
|
146
|
+
vault_mock: MagicMock,
|
147
|
+
mocker: MockerFixture,
|
148
|
+
secret_reader: MagicMock,
|
149
|
+
) -> None:
|
150
|
+
mocker.patch(
|
151
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.__init__"
|
152
|
+
).return_value = None
|
153
|
+
dms_integration = DeadMansSnitchIntegration()
|
154
|
+
dms_integration._secret_reader = secret_reader
|
155
|
+
dms_integration.settings = deadmanssnitch_settings
|
156
|
+
dms_integration.vault_client = vault_mock
|
157
|
+
mocker.patch("reconcile.deadmanssnitch.get_clusters_with_dms").return_value = [
|
158
|
+
ClusterV1(
|
159
|
+
name="create_cluster",
|
160
|
+
prometheusUrl="https://prometheus.create_cluster.devshift.net",
|
161
|
+
enableDeadMansSnitch=False,
|
162
|
+
alertmanagerUrl="alertmanager.create_cluster.devshift.net",
|
163
|
+
managedClusterRoles=True,
|
164
|
+
serverUrl="testurl",
|
165
|
+
consoleUrl="test_console",
|
166
|
+
)
|
167
|
+
]
|
168
|
+
mocker.patch(
|
169
|
+
"reconcile.deadmanssnitch.DeadMansSnitchApi.get_snitches"
|
170
|
+
).return_value = [
|
171
|
+
Snitch(
|
172
|
+
name="prometheus.create_cluster.devshift.net",
|
173
|
+
token="test",
|
174
|
+
href="testc",
|
175
|
+
status="healthy",
|
176
|
+
alert_type="basic",
|
177
|
+
alert_email=["test_mail"],
|
178
|
+
interval="15_minute",
|
179
|
+
check_in_url="test_url",
|
180
|
+
tags=["app-sre"],
|
181
|
+
notes="test_notes",
|
182
|
+
)
|
183
|
+
]
|
184
|
+
mock_delete_snitch = mocker.patch(
|
185
|
+
"reconcile.deadmanssnitch.DeadMansSnitchApi.delete_snitch"
|
186
|
+
)
|
187
|
+
mock_delete_snitch.return_value = None
|
188
|
+
dms_integration.run(dry_run=False)
|
189
|
+
mock_delete_snitch.assert_called_once_with("test")
|
190
|
+
|
191
|
+
|
192
|
+
def test_integration_for_update_vault(
|
193
|
+
deadmanssnitch_settings: DeadMansSnitchSettingsV1,
|
194
|
+
vault_mock: MagicMock,
|
195
|
+
mocker: MockerFixture,
|
196
|
+
secret_reader: MagicMock,
|
197
|
+
) -> None:
|
198
|
+
mocker.patch(
|
199
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.__init__"
|
200
|
+
).return_value = None
|
201
|
+
dms_integration = DeadMansSnitchIntegration()
|
202
|
+
dms_integration._secret_reader = secret_reader
|
203
|
+
dms_integration.settings = deadmanssnitch_settings
|
204
|
+
dms_integration.vault_client = vault_mock
|
205
|
+
mocker.patch("reconcile.deadmanssnitch.get_clusters_with_dms").return_value = [
|
206
|
+
ClusterV1(
|
207
|
+
name="test_cluster",
|
208
|
+
prometheusUrl="https://prometheus.create_cluster.devshift.net",
|
209
|
+
enableDeadMansSnitch=True,
|
210
|
+
alertmanagerUrl="alertmanager.create_cluster.devshift.net",
|
211
|
+
managedClusterRoles=True,
|
212
|
+
serverUrl="testurl",
|
213
|
+
consoleUrl="test_console",
|
214
|
+
)
|
215
|
+
]
|
216
|
+
mocker.patch(
|
217
|
+
"reconcile.deadmanssnitch.DeadMansSnitchApi.get_snitches"
|
218
|
+
).return_value = [
|
219
|
+
Snitch(
|
220
|
+
name="prometheus.create_cluster.devshift.net",
|
221
|
+
token="test",
|
222
|
+
href="testc",
|
223
|
+
status="healthy",
|
224
|
+
alert_type="basic",
|
225
|
+
alert_email=["test_mail"],
|
226
|
+
interval="15_minute",
|
227
|
+
check_in_url="test_url",
|
228
|
+
tags=["app-sre"],
|
229
|
+
notes="test_notes",
|
230
|
+
)
|
231
|
+
]
|
232
|
+
dms_integration.run(dry_run=False)
|
233
|
+
vault_mock.write.assert_called_once_with(
|
234
|
+
{
|
235
|
+
"path": deadmanssnitch_settings.snitches_path,
|
236
|
+
"data": {"deadmanssnitch-test_cluster-url": "test_url"},
|
237
|
+
},
|
238
|
+
decode_base64=False,
|
239
|
+
)
|
240
|
+
|
241
|
+
|
242
|
+
def test_integration_while_failed(
|
243
|
+
deadmanssnitch_settings: DeadMansSnitchSettingsV1,
|
244
|
+
vault_mock: MagicMock,
|
245
|
+
mocker: MockerFixture,
|
246
|
+
secret_reader: MagicMock,
|
247
|
+
) -> None:
|
248
|
+
mocker.patch(
|
249
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.__init__"
|
250
|
+
).return_value = None
|
251
|
+
dms_integration = DeadMansSnitchIntegration()
|
252
|
+
dms_integration._secret_reader = secret_reader
|
253
|
+
dms_integration.settings = deadmanssnitch_settings
|
254
|
+
dms_integration.vault_client = vault_mock
|
255
|
+
mocker.patch("reconcile.deadmanssnitch.get_clusters_with_dms").return_value = [
|
256
|
+
ClusterV1(
|
257
|
+
name="create_cluster",
|
258
|
+
prometheusUrl="https://prometheus.create_cluster.devshift.net",
|
259
|
+
enableDeadMansSnitch=True,
|
260
|
+
alertmanagerUrl="alertmanager.create_cluster.devshift.net",
|
261
|
+
managedClusterRoles=True,
|
262
|
+
serverUrl="testurl",
|
263
|
+
consoleUrl="test_console",
|
264
|
+
)
|
265
|
+
]
|
266
|
+
mocker.patch(
|
267
|
+
"reconcile.deadmanssnitch.DeadMansSnitchApi.get_snitches"
|
268
|
+
).return_value = []
|
269
|
+
mocker.patch(
|
270
|
+
"reconcile.deadmanssnitch.DeadMansSnitchIntegration.create_snitch",
|
271
|
+
side_effect=Exception("mock vault"),
|
272
|
+
)
|
273
|
+
with pytest.raises(ExceptionGroup):
|
274
|
+
dms_integration.run(dry_run=False)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from reconcile.gql_definitions.common.app_interface_dms_settings import (
|
4
|
+
DeadMansSnitchSettingsV1,
|
5
|
+
query,
|
6
|
+
)
|
7
|
+
from reconcile.utils import gql
|
8
|
+
from reconcile.utils.exceptions import AppInterfaceSettingsError
|
9
|
+
from reconcile.utils.gql import GqlApi
|
10
|
+
|
11
|
+
|
12
|
+
def get_deadmanssnitch_settings(
|
13
|
+
gql_api: Optional[GqlApi] = None,
|
14
|
+
) -> DeadMansSnitchSettingsV1:
|
15
|
+
api = gql_api if gql_api else gql.get_api()
|
16
|
+
data = query(query_func=api.query)
|
17
|
+
if data.settings and data.settings[0].dead_mans_snitch_settings is not None:
|
18
|
+
return data.settings[0].dead_mans_snitch_settings
|
19
|
+
raise AppInterfaceSettingsError("deadmanssnitch settings missing")
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from reconcile.gql_definitions.common.clusters_with_dms import (
|
4
|
+
ClusterV1,
|
5
|
+
query,
|
6
|
+
)
|
7
|
+
from reconcile.utils import gql
|
8
|
+
from reconcile.utils.gql import GqlApi
|
9
|
+
|
10
|
+
|
11
|
+
def get_clusters_with_dms(
|
12
|
+
gql_api: Optional[GqlApi] = None,
|
13
|
+
) -> list[ClusterV1]:
|
14
|
+
# get the clusters containing the filed enableDeadMansSnitch
|
15
|
+
variable = {"filter": {"enableDeadMansSnitch": {"ne": None}}}
|
16
|
+
api = gql_api if gql_api else gql.get_api()
|
17
|
+
data = query(query_func=api.query, variables=variable)
|
18
|
+
return data.clusters or []
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Optional,
|
5
|
+
Self,
|
6
|
+
)
|
7
|
+
|
8
|
+
import requests
|
9
|
+
from pydantic import BaseModel
|
10
|
+
|
11
|
+
BASE_URL = "https://api.deadmanssnitch.com/v1/snitches"
|
12
|
+
REQUEST_TIMEOUT = 60
|
13
|
+
|
14
|
+
|
15
|
+
class DeadManssnitchException(Exception):
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
class Snitch(BaseModel):
|
20
|
+
token: str
|
21
|
+
href: str
|
22
|
+
name: str
|
23
|
+
tags: list[str]
|
24
|
+
notes: str
|
25
|
+
status: str
|
26
|
+
check_in_url: str
|
27
|
+
interval: str
|
28
|
+
alert_type: str
|
29
|
+
alert_email: list[str]
|
30
|
+
vault_data: Optional[str]
|
31
|
+
|
32
|
+
def needs_vault_update(self) -> bool:
|
33
|
+
return self.vault_data is not None and self.check_in_url != self.vault_data
|
34
|
+
|
35
|
+
|
36
|
+
class DeadMansSnitchApi:
|
37
|
+
def __init__(
|
38
|
+
self, token: str, url: str = BASE_URL, timeout: int = REQUEST_TIMEOUT
|
39
|
+
) -> None:
|
40
|
+
self.token = token
|
41
|
+
self.url = url
|
42
|
+
self.timeout = timeout
|
43
|
+
self.session = requests.Session()
|
44
|
+
|
45
|
+
def __enter__(self) -> Self:
|
46
|
+
return self
|
47
|
+
|
48
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
49
|
+
self.session.close()
|
50
|
+
|
51
|
+
def get_snitches(self, tags: list[str]) -> list[Snitch]:
|
52
|
+
full_url = f"{self.url}?tags={','.join(tags)}"
|
53
|
+
logging.debug("Getting snitches for tags:%s", tags)
|
54
|
+
response = self.session.get(
|
55
|
+
url=full_url, auth=(self.token, ""), timeout=self.timeout
|
56
|
+
)
|
57
|
+
response.raise_for_status()
|
58
|
+
snitches = [Snitch(**item) for item in response.json()]
|
59
|
+
return snitches
|
60
|
+
|
61
|
+
def create_snitch(self, payload: dict) -> Snitch:
|
62
|
+
if payload.get("name") is None or payload.get("interval") is None:
|
63
|
+
raise DeadManssnitchException(
|
64
|
+
"Invalid payload,name and interval are mandatory"
|
65
|
+
)
|
66
|
+
headers = {"Content-Type": "application/json"}
|
67
|
+
logging.debug("Creating new snitch with name:: %s ", payload["name"])
|
68
|
+
response = self.session.post(
|
69
|
+
url=self.url,
|
70
|
+
json=payload,
|
71
|
+
auth=(self.token, ""),
|
72
|
+
headers=headers,
|
73
|
+
timeout=self.timeout,
|
74
|
+
)
|
75
|
+
response.raise_for_status()
|
76
|
+
response_json = response.json()
|
77
|
+
return Snitch(**response_json)
|
78
|
+
|
79
|
+
def delete_snitch(self, token: str) -> None:
|
80
|
+
delete_api_url = f"{self.url}/{token}"
|
81
|
+
response = self.session.delete(
|
82
|
+
url=delete_api_url, auth=(self.token, ""), timeout=self.timeout
|
83
|
+
)
|
84
|
+
response.raise_for_status()
|
85
|
+
logging.debug("Successfully deleted snich: %s", token)
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc706.dist-info → qontract_reconcile-0.10.1rc708.dist-info}/top_level.txt
RENAMED
File without changes
|