finished and working
This commit is contained in:
parent
cf025c271d
commit
7d73544d0a
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
.direnv/
|
||||
.mypy_cache/
|
||||
.mypy_cache/
|
||||
/result
|
||||
/config.json
|
||||
/config.log
|
||||
/connection.log
|
80
README.md
80
README.md
@ -0,0 +1,80 @@
|
||||
# Outage Detector
|
||||
|
||||
This script pings a host and logs whether a connection could be established.
|
||||
It then can also extract the major outages from these logs.
|
||||
It is mainly used as a tool for statistical analysis of residential internet connections.
|
||||
|
||||
## Usage
|
||||
|
||||
You can set these options in a json file and pass it with the `--config` option.
|
||||
Additional command line parameters override options defined in a config.
|
||||
|
||||
```txt
|
||||
If no argument is passed then de defaults are assumed.
|
||||
|
||||
--config Path to a config file in JSON format.
|
||||
To set a command line argument, use it as a key and set its value.
|
||||
Config is not used unless explicitly set.
|
||||
|
||||
--log Path to a the file where results should be appended to.
|
||||
Created the file if it does not already exist.
|
||||
Default: ./connection.log
|
||||
|
||||
--host IP or hostname of a server to query.
|
||||
Default: 1.1.1.1
|
||||
|
||||
--timeout Set the timeout in seconds to use for the connection test
|
||||
Default: 2
|
||||
|
||||
--outages Print the outages to stdout.
|
||||
Set the lenght in minutes where a connection loss is condidered an outage
|
||||
This option can only be used with --log.
|
||||
Default: 3
|
||||
|
||||
--stdout Return the resulting logline in the terminal.
|
||||
Default Behaviour: Do not print to stdout.
|
||||
|
||||
--help Print this menu
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
Save an example config options as a `.json` file with this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"timeout": null, // To use the default value of 2
|
||||
"log": "./connection.log",
|
||||
}
|
||||
```
|
||||
|
||||
### Example 1
|
||||
|
||||
```txt
|
||||
$ outage_detector --config ./config.json --host 1.1.1.1 --log --stdout
|
||||
> [YYYY-MM-DD HH:MM:SS][1.1.1.1][OK]
|
||||
```
|
||||
|
||||
Loads values from `./config.json`.
|
||||
Overrides `host` with `example.com`.
|
||||
Appends the result to `log.txt` and prints it to `stdout`
|
||||
|
||||
```txt
|
||||
$ outage_detector --config ./config.json --host example.com --log another.log --stdout
|
||||
> [YYYY-MM-DD HH:MM:SS][1.1.1.1][FAIL]
|
||||
```
|
||||
|
||||
Loads values from `./config.json`.
|
||||
Overrides `host` with `example.com`.
|
||||
Appends the result to `another.log` and prints it to `stdout`
|
||||
|
||||
### Example 2
|
||||
|
||||
```txt
|
||||
$ outage_detector --log ./outage-detector.log --outages 5
|
||||
> [YYYY-MM-DD HH:MM:SS][1.1.1.1] lasting for X Hours and Y Minutes
|
||||
> [YYYY-MM-DD HH:MM:SS][1.1.1.1] lasting for X Hours and Y Minutes
|
||||
```
|
||||
|
||||
Print to `stdout` all outages from `./outage-detector.log` in chronological order that lasted at least 5 minutes.
|
@ -41,9 +41,10 @@
|
||||
};
|
||||
});
|
||||
|
||||
# Home Manager Module
|
||||
# TODO: Home Manager Module
|
||||
# homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||
|
||||
# NixOS Module
|
||||
# nixosModules.default = import ./nix/module.nix inputs;
|
||||
nixosModules.default = import ./nix/module.nix inputs;
|
||||
};
|
||||
}
|
||||
|
66
nix/module.nix
Normal file
66
nix/module.nix
Normal file
@ -0,0 +1,66 @@
|
||||
inputs: {
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}: let
|
||||
cfg = config.outage-detector;
|
||||
package = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
inherit (lib) mkIf mkEnableOption mkOption types;
|
||||
|
||||
format = pkgs.formats.json {};
|
||||
configFile = format.generate "config.json" cfg.settings;
|
||||
in {
|
||||
options.outage-detector = {
|
||||
enable = mkEnableOption "outage-detector";
|
||||
|
||||
timer = lib.mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
The time intervall in seconds the script should be repeated.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; let
|
||||
valueType = nullOr (oneOf [
|
||||
# TODO: restrict type to actual config file structure
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
]);
|
||||
in
|
||||
valueType;
|
||||
default = throw "Please specify outage-detector.settings";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [package];
|
||||
|
||||
systemd.services.outage-detector = mkIf (cfg.timer
|
||||
!= null) {
|
||||
script = "${package}/bin/outage_detector --config ${configFile}";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.outage-detector = mkIf (cfg.timer
|
||||
!= null) {
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig = {
|
||||
OnBootSec = "0s";
|
||||
OnUnitActiveSec = "${toString cfg.timer}s";
|
||||
Unit = "outage-detector.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
393
outage_detector/main.py
Executable file → Normal file
393
outage_detector/main.py
Executable file → Normal file
@ -1,99 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
# Utility to determine the networks connection status by pinging IPs and logging the result
|
||||
# It can also output a list of outages
|
||||
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
from ipaddress import ip_address
|
||||
import socket
|
||||
import time
|
||||
from sys import argv
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class LogEntry:
|
||||
"""Class describing a connection attempt"""
|
||||
|
||||
def __init__(self: 'LogEntry', log_entry: str) -> None:
|
||||
self.date = datetime.strptime(log_entry[0:19], '%Y-%m-%d %H:%M:%S')
|
||||
self.ip = log_entry.split('[')[1].split(']')[0]
|
||||
self.date = datetime.strptime(log_entry[1:20], '%Y-%m-%d %H:%M:%S')
|
||||
self.host = log_entry.split('[')[2].split(']')[0]
|
||||
self.failed = 'FAIL' in log_entry
|
||||
|
||||
|
||||
def isValidIP(ip: str) -> bool:
|
||||
"""Check if a string is a valid IP"""
|
||||
def printOutages(config: dict) -> None:
|
||||
"""Print outages from a log file"""
|
||||
|
||||
try:
|
||||
ip_address(ip)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def isReachable(ip: str = '1.1.1.1', port: int = 80, timeout: float = 2) -> bool:
|
||||
"""Return wether an IP is reachable
|
||||
modified from source: https://gist.github.com/betrcode/0248f0fda894013382d7?permalink_comment_id=4438869#gistcomment-4438869
|
||||
"""
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout)
|
||||
|
||||
try:
|
||||
# True if open, False if not
|
||||
reachable = s.connect_ex((ip, int(port))) == 0
|
||||
if reachable:
|
||||
s.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
reachable = False
|
||||
s.close()
|
||||
|
||||
return reachable
|
||||
|
||||
|
||||
def logConnectionStatus(log_path: str = './log.txt', ips: list[str] = ['1.1.1.1'], port: int = 80, timeout: float = 2, dry_run: bool = False) -> list[str]:
|
||||
"""Log IP, time and result of a connection check
|
||||
|
||||
Output has this format:
|
||||
2023-01-19 07:02:01 [1.0.0.1] [FAIL]
|
||||
2023-01-19 07:03:01 [1.1.1.1] [FAIL]
|
||||
2023-01-19 07:04:01 [1.1.1.1] [OK]
|
||||
2023-01-19 07:05:02 [1.1.1.1] [OK]
|
||||
"""
|
||||
|
||||
log_lines = [
|
||||
f"{time.strftime('%Y-%m-%d %H:%M:%S')} [{ip}] [{'OK' if isReachable(ip) else 'FAIL'}]" for ip in ips]
|
||||
|
||||
if not dry_run:
|
||||
# TODO: move this into a log function which is passed as an argument
|
||||
with open(log_path, 'a') as file:
|
||||
file.writelines(log_line + '\n' for log_line in log_lines)
|
||||
|
||||
return log_lines
|
||||
|
||||
|
||||
def printOutages(log_path: str = './log.txt') -> None:
|
||||
"""Prints out the Outages with duration and ip sorted by starttime
|
||||
|
||||
Output has this format:
|
||||
[1.1.1.1] 2023-01-19 06:29:01 lasting for 15 Minutes
|
||||
[1.0.0.1] 2023-01-19 06:45:01 lasting for 1 Hours and 19 Minutes
|
||||
[1.1.1.1] 2023-01-19 07:05:01 lasting for 2 Hours and 3 Minutes
|
||||
"""
|
||||
|
||||
with open(log_path, 'r') as file:
|
||||
with open(config['log'], 'r') as file:
|
||||
log = list(map(lambda x: LogEntry(x), file.readlines()))
|
||||
|
||||
# cluster log entries by IP
|
||||
log_by_ip = {}
|
||||
# cluster log entries by host
|
||||
log_by_host = {}
|
||||
for entry in log:
|
||||
if entry.ip not in log_by_ip:
|
||||
log_by_ip[entry.ip] = [entry]
|
||||
if entry.host not in log_by_host:
|
||||
log_by_host[entry.host] = [entry]
|
||||
else:
|
||||
log_by_ip[entry.ip].append(entry)
|
||||
log_by_host[entry.host].append(entry)
|
||||
|
||||
# cluster fails to outages
|
||||
# we consider the time of subsequent failed connection attemts to the same ip an outage
|
||||
outages: list[list[LogEntry]] = []
|
||||
# we consider the time of subsequent failed connection attemts to the same host an outage
|
||||
outages: list = []
|
||||
last_failed = False
|
||||
for entries in log_by_ip.values():
|
||||
for entries in log_by_host.values():
|
||||
for entry in entries:
|
||||
if entry.failed and last_failed:
|
||||
outages[-1].append(entry)
|
||||
@ -109,163 +48,219 @@ def printOutages(log_path: str = './log.txt') -> None:
|
||||
# get duration of outage in hours and minutes
|
||||
outage_duration = round(
|
||||
(outage[-1].date - outage[0].date).seconds / 60)
|
||||
# continue if outage is shorter than the minimum duration
|
||||
if outage_duration < config['outages']:
|
||||
continue
|
||||
hours = outage_duration // 60
|
||||
minutes = outage_duration - (hours * 60)
|
||||
ip = outage[0].ip
|
||||
host = outage[0].host
|
||||
|
||||
# Outputs outages in the form of "Outage at: 2023-19-01 06:29:01 lasting for 2 Hours and 39 Minutes"
|
||||
print(f"[{ip}] {outage[0].date} lasting for {'{} Hours and '.format(hours) if hours >= 1 else ''}{minutes} Minutes")
|
||||
print(f"[{outage[0].date}][{host}] lasting for {'{} Hours and '.format(hours) if hours >= 1 else ''}{minutes} Minutes")
|
||||
|
||||
|
||||
def getSanitizedArguments() -> dict:
|
||||
"""Read and sanitize command line arguments"""
|
||||
def isOnline(host: str, timeout: int) -> bool:
|
||||
"""Check if connection to a host can be established"""
|
||||
|
||||
cli_options: dict[str, Any] = {}
|
||||
try:
|
||||
requests.head(f"https://{host}", timeout=timeout)
|
||||
return True
|
||||
except requests.ConnectionError:
|
||||
return False
|
||||
|
||||
# get command-line parameters and arguments
|
||||
if len(argv) > 1:
|
||||
buffer = ''
|
||||
|
||||
# loop through the arguments and create a dictionary where
|
||||
# elements starting with a '--' are considered keys
|
||||
# and the elements folowwing items
|
||||
for arg in argv[1:]: # remove the first argument (filepath)
|
||||
if arg.startswith('--'):
|
||||
buffer = arg.removeprefix('--')
|
||||
def log(config: dict) -> None:
|
||||
"""Log the connection status of a host"""
|
||||
|
||||
# ensures that the same parameter can be passed twice and the arguments get joined
|
||||
if not buffer in cli_options.keys():
|
||||
cli_options[buffer] = []
|
||||
else:
|
||||
cli_options[buffer].append(arg)
|
||||
logline = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}][{config['host']}][{'OK' if isOnline(config['host'], config['timeout']) else 'FAIL'}]\n"
|
||||
|
||||
# sanitize args
|
||||
# 'OPTION_NAME': number of permitted arguments
|
||||
# -1 for any number of arguments
|
||||
permitted_options = {
|
||||
'help': 0, 'log_path': 1, 'ip': -1, 'port': 1, 'timeout': 1, 'log': 0, 'outages': 0, 'test': 0
|
||||
}
|
||||
if config['log'] != None:
|
||||
with open(config['log'], 'a') as file:
|
||||
file.write(logline)
|
||||
|
||||
# check if non supported parameters have been passed
|
||||
unknown_parameters = cli_options.keys() - permitted_options.keys()
|
||||
if unknown_parameters != set():
|
||||
if config['stdout']:
|
||||
print(logline)
|
||||
|
||||
|
||||
def printHelp() -> None:
|
||||
"""Prints the help menu"""
|
||||
|
||||
print(
|
||||
"""
|
||||
If no argument is passed then de defaults are assumed.
|
||||
|
||||
--config Path to a config file in JSON format.
|
||||
To set a command line argument, use it as a key and set its value.
|
||||
Config is nor used unless explicitly set.
|
||||
|
||||
--log Path to a the file where results should be appended to.
|
||||
Created the file if it does not already exist.
|
||||
Default: ./connection.log
|
||||
|
||||
--host IP or hostname of a server to query.
|
||||
Default: 1.1.1.1
|
||||
|
||||
--timeout Set the timeout in seconds to use for the connection test
|
||||
Default: 2
|
||||
|
||||
--outages Print the outages to stdout.
|
||||
Set the lenght in minutes where a connection loss is condidered an outage
|
||||
This option can only be used with --log.
|
||||
Default: 3
|
||||
|
||||
--stdout Return the resulting logline in the terminal.
|
||||
Default Behaviour: Do not print to stdout.
|
||||
|
||||
--help Print this menu
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def loadConfig() -> tuple[dict, str]:
|
||||
"""Load and validate the config and return it as a dict together with the execution mode"""
|
||||
|
||||
argv = sys.argv
|
||||
|
||||
config: dict = {}
|
||||
buffer = ''
|
||||
mode = 'log'
|
||||
|
||||
# parse arguments
|
||||
for arg in argv[1:]:
|
||||
if arg.startswith('--'):
|
||||
buffer = arg.removeprefix('--')
|
||||
config[buffer] = []
|
||||
else:
|
||||
config[buffer].append(arg)
|
||||
|
||||
# print help if requested
|
||||
if 'help' in config:
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
## Validate Arguments ##
|
||||
|
||||
# right arguments
|
||||
valid_parameters = {'config', 'log', 'host',
|
||||
'timeout', 'outages', 'stdout', 'help'}
|
||||
if (unknown_parameters := config.keys() - valid_parameters) != set():
|
||||
print(f"[ERROR] Unknown parameters: {unknown_parameters}")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
# check if the right number of arguments has been passed
|
||||
for key in cli_options:
|
||||
if permitted_options[key] == -1:
|
||||
continue
|
||||
if len(cli_options[key]) != permitted_options[key]:
|
||||
print(f"[ERROR] '{key}' expected {permitted_options[key]} arguments but got {len(cli_options[key])}")
|
||||
printHelp()
|
||||
quit()
|
||||
# number of arguments
|
||||
maximum_arguments = {
|
||||
'config': 1,
|
||||
'log': 1,
|
||||
'host': 1,
|
||||
'timeout': 1,
|
||||
'outages': 1,
|
||||
'stdout': 0,
|
||||
'help': 0
|
||||
}
|
||||
|
||||
### parameter-specific checks
|
||||
|
||||
# log_path has only one argument at this point so it's safe to make the list into a string
|
||||
if 'log_path' in cli_options:
|
||||
cli_options['log_path'] = cli_options['log_path'][0]
|
||||
|
||||
# check if all IPs are of valid format
|
||||
if 'ip' in cli_options:
|
||||
for ip in cli_options['ip']:
|
||||
if not isValidIP(ip): # TODO: just use a regex for IPv4
|
||||
print(f"[ERROR] '{ip}' is not a IP address.")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
# check if port is an integer in the valid port range [0-65535]
|
||||
if 'port' in cli_options:
|
||||
try:
|
||||
cli_options['port'] = int(cli_options['port'][0])
|
||||
except ValueError:
|
||||
for key in config:
|
||||
if len(config[key]) > maximum_arguments[key]:
|
||||
print(
|
||||
f"[ERROR] 'port' expects an integer between 0 and 65535. Got '{cli_options['port'][0]}'")
|
||||
f"[ERROR] Too many arguments for '{key}'. Expected 0{' or 1' if maximum_arguments[key] == 1 else ''}. Got: {len(config[key])}")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
if not (0 <= cli_options['port'] and cli_options['port'] <= 65535):
|
||||
print(f"[ERROR] 'PORT' outside of valid range 0 and 65535")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
# check if timeout is a valid float larger than 0
|
||||
if 'timeout' in cli_options:
|
||||
try:
|
||||
cli_options['timeout'] = float(cli_options['timeout'][0])
|
||||
except ValueError:
|
||||
# check that outages is only used with --log
|
||||
if 'outages' in config:
|
||||
mode = 'outages'
|
||||
if config.keys() - {'outages', 'log'} != set():
|
||||
print(
|
||||
f"[ERROR] 'timeout' expects a value between float value. Got '{cli_options['timeout'][0]}'")
|
||||
f"[ERROR] --outages can only be used alone or with --log")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
if cli_options['timeout'] <= 0:
|
||||
print(f"[ERROR] 'timeout' needs to be larger than 0")
|
||||
# After that check has passed remove the list
|
||||
for key in config:
|
||||
config[key] = config[key][0] if len(config[key]) == 1 else None
|
||||
|
||||
# set stdout
|
||||
config['stdout'] = 'stdout' in config
|
||||
|
||||
# if config is passed then load it
|
||||
if 'config' in config:
|
||||
try:
|
||||
config_file = './config.json' if config['config'] == None else config['config']
|
||||
with open(config_file, 'r') as file:
|
||||
# this gives the stdout toggle behaviour
|
||||
config_file = json.load(file)
|
||||
|
||||
# merge config_file and config
|
||||
# this also subtly overrites no-stdout to whatever the config file says
|
||||
for key in config_file.keys() & config.keys():
|
||||
if key == 'stdout':
|
||||
continue
|
||||
if config[key] == None:
|
||||
config[key] = config_file[key]
|
||||
|
||||
config_file.update(config)
|
||||
config = config_file
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"[ERROR] Config file not found: {config['config']}")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
# check if HELP is has been passed
|
||||
if 'help' in cli_options:
|
||||
# load defaults, if not set
|
||||
defaults = {
|
||||
'log': './connection.log',
|
||||
'host': '1.1.1.1',
|
||||
'timeout': 2,
|
||||
'outages': 3,
|
||||
'stdout': False,
|
||||
'help': None
|
||||
}
|
||||
for key in defaults:
|
||||
if key == 'log':
|
||||
if config['stdout'] and 'log' not in config:
|
||||
config['log'] = None
|
||||
continue
|
||||
|
||||
if (key not in config) or (key in config and config[key] == None):
|
||||
config[key] = defaults[key]
|
||||
|
||||
# timeout
|
||||
try:
|
||||
config['timeout'] = int(config['timeout'])
|
||||
except ValueError:
|
||||
print(
|
||||
f"[ERROR] Invalid type for 'timeout'. Expected int. Got: {type(config['timeout'])}")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
return cli_options
|
||||
if config['timeout'] <= 0:
|
||||
print(f"[ERROR] 'timeout' needs to be larger than 0")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
# outages
|
||||
try:
|
||||
config['outages'] = int(config['outages'])
|
||||
except ValueError:
|
||||
print(
|
||||
f"[ERROR] Invalid type for 'outages'. Expected int. Got: {type(config['outages'])}")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
def printHelp() -> None:
|
||||
print(
|
||||
"""
|
||||
Utility to determine the networks connection status by pinging IPs and logging the result.
|
||||
if config['outages'] < 0:
|
||||
print(f"[ERROR] 'outages' needs to be larger than 0")
|
||||
printHelp()
|
||||
quit()
|
||||
|
||||
--log_path path to a the file where test results should be logged
|
||||
Default: log.txt
|
||||
return config, mode
|
||||
|
||||
--ip space separated list of IPs to ping and test
|
||||
Default: 1.1.1.1
|
||||
|
||||
--port set the port to use for the conneciton test
|
||||
Default: 80
|
||||
|
||||
--timeout set the timeout in seconds to use for the connection test
|
||||
Default: 2
|
||||
|
||||
--test test connection and return result to terminal
|
||||
use with --LOG to also log the result in a file
|
||||
|
||||
--log test connection and log to --LOG_PATH
|
||||
use with --TEST to also print the reult to the terminal
|
||||
|
||||
--outages print the dates and lenghts of outages
|
||||
|
||||
--help print this menu
|
||||
"""
|
||||
)
|
||||
|
||||
def main():
|
||||
|
||||
args = getSanitizedArguments()
|
||||
|
||||
# set variables based on user input or use the defaults
|
||||
log_path = args['log_path'] if 'log_path' in args else './log.txt'
|
||||
ips = args['ip'] if 'ip' in args else ['1.1.1.1']
|
||||
port = args['port'] if 'port' in args else 80
|
||||
timeout = args['timeout'] if 'timeout' in args else 2
|
||||
|
||||
# perform functionality based on user input
|
||||
if 'outages' in args:
|
||||
printOutages(log_path)
|
||||
|
||||
if 'log' in args and 'test' in args:
|
||||
for log_line in logConnectionStatus(log_path = log_path, ips = ips, port = port, timeout = timeout): print(log_line)
|
||||
|
||||
if 'log' in args and 'test' not in args:
|
||||
logConnectionStatus(log_path = log_path, ips = ips, port = port, timeout = timeout)
|
||||
|
||||
if 'test' in args and 'log' not in args:
|
||||
for log_line in logConnectionStatus(ips = ips, port = port, timeout = timeout, dry_run = True): print(log_line)
|
||||
|
||||
config, mode = loadConfig()
|
||||
if mode == 'log':
|
||||
log(config)
|
||||
elif mode == 'outages':
|
||||
printOutages(config)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
151
poetry.lock
generated
151
poetry.lock
generated
@ -1,7 +1,154 @@
|
||||
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||
package = []
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2023.7.22"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
|
||||
{file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.2.0"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"},
|
||||
{file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"},
|
||||
{file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"},
|
||||
{file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"},
|
||||
{file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"},
|
||||
{file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"},
|
||||
{file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.29.0"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"},
|
||||
{file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<4"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.16"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
files = [
|
||||
{file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"},
|
||||
{file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "53f2eabc9c26446fbcc00d348c47878e118afc2054778c3c803a0a8028af27d9"
|
||||
content-hash = "ee132bd4e431294d08ddbc6bd9a59272e394e2a275ec7982d40592401aa07350"
|
||||
|
@ -4,10 +4,11 @@ version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Kristian Krsnik <git@krsnik.at>"]
|
||||
readme = "README.md"
|
||||
packages = [{include = "outage_detector"}]
|
||||
packages = [{ include = "outage_detector" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
requests = "2.29.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
outage_detector = "outage_detector.main:main"
|
||||
|
Loading…
Reference in New Issue
Block a user