Skip to content

Commit d57279a

Browse files
committed
Added key_days_to_expire value to output
1 parent bcb27cd commit d57279a

File tree

3 files changed

+26
-8
lines changed

3 files changed

+26
-8
lines changed

.github/workflows/publish-image.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ jobs:
2727
run: |
2828
docker push laitco/tailscale-healthcheck:latest
2929
30-
- name: Build Docker image 1.2.1
30+
- name: Build Docker image 1.2.2
3131
run: |
32-
docker build -t laitco/tailscale-healthcheck:1.2.1 .
32+
docker build -t laitco/tailscale-healthcheck:1.2.2 .
3333
34-
- name: Validate Docker image 1.2.1
34+
- name: Validate Docker image 1.2.2
3535
run: |
36-
docker run --rm laitco/tailscale-healthcheck:1.2.1 --help
36+
docker run --rm laitco/tailscale-healthcheck:1.2.2 --help
3737
38-
- name: Push Docker image 1.2.1
38+
- name: Push Docker image 1.2.2
3939
run: |
40-
docker push laitco/tailscale-healthcheck:1.2.1
40+
docker push laitco/tailscale-healthcheck:1.2.2

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ A Python-based Flask application to monitor the health of devices in a Tailscale
4848
- **Overall Health Status**: Combined health status based on:
4949
- Device online status (`online_healthy`)
5050
- Device key expiry status (`key_healthy`)
51+
- Days until key expiry (`key_days_to_expire`)
5152
- **Global Health Metrics**:
5253
- Global device health status (`global_healthy`)
5354
- Global online status (`global_online_healthy`)
@@ -61,6 +62,10 @@ A Python-based Flask application to monitor the health of devices in a Tailscale
6162

6263
## 📝 Release Notes
6364

65+
### 1.2.2
66+
- Added `key_days_to_expire` field showing the number of days until a device's key expires
67+
- Set to `null` when key expiry is disabled
68+
6469
### 1.2.1
6570
- Added global health metrics with configurable thresholds
6671
- GLOBAL_HEALTHY_THRESHOLD (default: 100)
@@ -130,6 +135,7 @@ Returns the health status of all devices.
130135
"keyExpiryDisabled": false,
131136
"keyExpiryTimestamp": "2025-05-09T22:03:57+02:00",
132137
"key_healthy": true,
138+
"key_days_to_expire": 25,
133139
"healthy": true
134140
}
135141
],
@@ -323,7 +329,7 @@ endpoints:
323329
interval: 5m
324330
conditions:
325331
- "[STATUS] == 200"
326-
- "[BODY].healthy == pat(*true*)"
332+
- "[BODY].device.healthy == pat(*true*)"
327333
alerts:
328334
- type: email
329335
failure-threshold: 2
@@ -340,7 +346,7 @@ endpoints:
340346
- **`interval`**: The frequency of the healthcheck (e.g., every 5 minutes).
341347
- **`conditions`**:
342348
- `[STATUS] == 200`: Ensures the HTTP status code is `200`.
343-
- `[BODY].healthy == pat(*true*)`: Checks if the `healthy` field in the response body is `true`.
349+
- `[BODY].device.healthy == pat(*true*)`: Checks if the `healthy` field in the response body is `true`.
344350
- **`alerts`**:
345351
- **`type`**: The type of alert (e.g., `email`).
346352
- **`failure-threshold`**: The number of consecutive failures before triggering an alert.

healthcheck.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,13 @@ def health_check():
161161
last_seen_local = last_seen.astimezone(tz)
162162
expires = None
163163
key_healthy = True if device.get("keyExpiryDisabled", False) else True
164+
key_days_to_expire = None
164165
if not device.get("keyExpiryDisabled", False) and device.get("expires"):
165166
expires = datetime.strptime(device["expires"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC)
166167
expires = expires.astimezone(tz)
167168
time_until_expiry = expires - datetime.now(tz)
168169
key_healthy = time_until_expiry.total_seconds() / 60 > KEY_THRESHOLD_MINUTES
170+
key_days_to_expire = time_until_expiry.days
169171

170172
online_is_healthy = last_seen_local >= threshold_time
171173
is_healthy = online_is_healthy and key_healthy
@@ -196,6 +198,7 @@ def health_check():
196198
"online_healthy": online_is_healthy,
197199
"keyExpiryDisabled": device.get("keyExpiryDisabled", False),
198200
"key_healthy": key_healthy,
201+
"key_days_to_expire": key_days_to_expire,
199202
"healthy": is_healthy
200203
}
201204

@@ -277,11 +280,13 @@ def health_check_by_identifier(identifier):
277280
last_seen_local = last_seen.astimezone(tz) # Convert lastSeen to the specified timezone
278281
expires = None
279282
key_healthy = True if device.get("keyExpiryDisabled", False) else True
283+
key_days_to_expire = None
280284
if not device.get("keyExpiryDisabled", False) and device.get("expires"):
281285
expires = datetime.strptime(device["expires"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC)
282286
expires = expires.astimezone(tz)
283287
time_until_expiry = expires - datetime.now(tz)
284288
key_healthy = time_until_expiry.total_seconds() / 60 > KEY_THRESHOLD_MINUTES
289+
key_days_to_expire = time_until_expiry.days
285290

286291
logging.debug(f"Device {device['name']} last seen (local): {last_seen_local.isoformat()}")
287292
online_is_healthy = last_seen_local >= threshold_time
@@ -304,6 +309,7 @@ def health_check_by_identifier(identifier):
304309
"online_healthy": online_is_healthy,
305310
"keyExpiryDisabled": device.get("keyExpiryDisabled", False),
306311
"key_healthy": key_healthy,
312+
"key_days_to_expire": key_days_to_expire,
307313
"healthy": online_is_healthy and key_healthy
308314
}
309315

@@ -373,11 +379,13 @@ def health_check_unhealthy():
373379
last_seen_local = last_seen.astimezone(tz) # Convert lastSeen to the specified timezone
374380
expires = None
375381
key_healthy = True if device.get("keyExpiryDisabled", False) else True
382+
key_days_to_expire = None
376383
if not device.get("keyExpiryDisabled", False) and device.get("expires"):
377384
expires = datetime.strptime(device["expires"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC)
378385
expires = expires.astimezone(tz)
379386
time_until_expiry = expires - datetime.now(tz)
380387
key_healthy = time_until_expiry.total_seconds() / 60 > KEY_THRESHOLD_MINUTES
388+
key_days_to_expire = time_until_expiry.days
381389

382390
logging.debug(f"Device {device['name']} last seen (local): {last_seen_local.isoformat()}")
383391
online_is_healthy = last_seen_local >= threshold_time
@@ -405,6 +413,7 @@ def health_check_unhealthy():
405413
"online_healthy": online_is_healthy,
406414
"keyExpiryDisabled": device.get("keyExpiryDisabled", False),
407415
"key_healthy": key_healthy,
416+
"key_days_to_expire": key_days_to_expire,
408417
"healthy": online_is_healthy and key_healthy
409418
}
410419

@@ -472,11 +481,13 @@ def health_check_healthy():
472481
last_seen_local = last_seen.astimezone(tz) # Convert lastSeen to the specified timezone
473482
expires = None
474483
key_healthy = True if device.get("keyExpiryDisabled", False) else True
484+
key_days_to_expire = None
475485
if not device.get("keyExpiryDisabled", False) and device.get("expires"):
476486
expires = datetime.strptime(device["expires"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC)
477487
expires = expires.astimezone(tz)
478488
time_until_expiry = expires - datetime.now(tz)
479489
key_healthy = time_until_expiry.total_seconds() / 60 > KEY_THRESHOLD_MINUTES
490+
key_days_to_expire = time_until_expiry.days
480491

481492
logging.debug(f"Device {device['name']} last seen (local): {last_seen_local.isoformat()}")
482493
online_is_healthy = last_seen_local >= threshold_time
@@ -498,6 +509,7 @@ def health_check_healthy():
498509
"online_healthy": online_is_healthy,
499510
"keyExpiryDisabled": device.get("keyExpiryDisabled", False),
500511
"key_healthy": key_healthy,
512+
"key_days_to_expire": key_days_to_expire,
501513
"healthy": online_is_healthy and key_healthy
502514
}
503515

0 commit comments

Comments
 (0)