sysview-py 0.1.0__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.
sysview_py/__init__.py ADDED
File without changes
sysview_py/db.py ADDED
@@ -0,0 +1,122 @@
1
+ import datetime
2
+ import sqlite3
3
+
4
+
5
+ class SysviewDB:
6
+ schema = """
7
+ CREATE TABLE hosts(name, UNIQUE(name));
8
+ CREATE TABLE types(name, UNIQUE(name));
9
+ CREATE TABLE hosts_types(host, type, UNIQUE(host, type));
10
+ CREATE TABLE reports(hostname, type, date, raw_report);
11
+ CREATE TABLE items(command, status, status_text, additional_text, raw_item, report_id);
12
+ """
13
+ def __init__(self, path):
14
+ self.path = path
15
+ self._connection = None
16
+ self._cursor = None
17
+
18
+ def connect(self):
19
+ db_exists = False
20
+ if self.path.exists():
21
+ db_exists = True
22
+ self._connection = sqlite3.connect(self.path)
23
+ self._cursor = self._connection.cursor()
24
+ if not db_exists:
25
+ self.init()
26
+
27
+ def init(self):
28
+ self._cursor.executescript(self.schema)
29
+ self._connection.commit()
30
+
31
+ def get_hosts(self):
32
+ hosts = []
33
+ hostnames = self._cursor.execute("SELECT name FROM hosts").fetchall()
34
+ hostnames = (host[0] for host in hostnames)
35
+ for hostname in hostnames:
36
+ if hostname is None:
37
+ continue
38
+ types = self.get_types(hostname)
39
+ hosts.append((hostname, types))
40
+ return hosts
41
+
42
+ def get_types(self, hostname):
43
+ sql = f"SELECT type FROM hosts_types WHERE host = '{hostname}'"
44
+ report_types = self._cursor.execute(sql).fetchall()
45
+ report_types = [report_type[0] for report_type in report_types]
46
+ return report_types
47
+
48
+ def get_latest_reports(self, hostname):
49
+ reports = []
50
+ host_types = self.get_types(hostname)
51
+ for host_type in host_types:
52
+ sql = f"SELECT rowid,raw_report from reports WHERE type = '{host_type}' AND hostname= '{hostname}' ORDER BY rowid DESC LIMIT 1"
53
+ latest_report = self._cursor.execute(sql).fetchone()
54
+ reports.append(latest_report)
55
+ return reports
56
+
57
+ def add(self, report):
58
+ report_data = (report.hostname, report.type, report.date, report.raw_report)
59
+ self._cursor.execute("INSERT INTO reports VALUES (?, ?, ?, ?)", report_data)
60
+ report_id = self._cursor.lastrowid
61
+
62
+ self._cursor.execute(
63
+ "INSERT OR IGNORE INTO hosts VALUES (?)", (report.hostname,)
64
+ )
65
+ self._cursor.execute("INSERT OR IGNORE INTO types VALUES (?)", (report.type,))
66
+ self._cursor.execute(
67
+ "INSERT OR IGNORE INTO hosts_types VALUES (?,?)",
68
+ (report.hostname, report.type),
69
+ )
70
+
71
+ items = [
72
+ (
73
+ i.command,
74
+ i.status,
75
+ i.status_text,
76
+ i.additional_text,
77
+ i.raw_item,
78
+ report_id,
79
+ )
80
+ for i in report.items
81
+ ]
82
+ self._cursor.executemany("INSERT INTO items VALUES(?, ?, ?, ?, ?, ?)", items)
83
+
84
+ self._connection.commit()
85
+
86
+
87
+ def adapt_date_iso(val):
88
+ """Adapt datetime.date to ISO 8601 date."""
89
+ return val.isoformat()
90
+
91
+
92
+ def adapt_datetime_iso(val):
93
+ """Adapt datetime.datetime to timezone-naive ISO 8601 date."""
94
+ return val.replace(tzinfo=None).isoformat()
95
+
96
+
97
+ def adapt_datetime_epoch(val):
98
+ """Adapt datetime.datetime to Unix timestamp."""
99
+ return int(val.timestamp())
100
+
101
+
102
+ def convert_date(val):
103
+ """Convert ISO 8601 date to datetime.date object."""
104
+ return datetime.date.fromisoformat(val.decode())
105
+
106
+
107
+ def convert_datetime(val):
108
+ """Convert ISO 8601 datetime to datetime.datetime object."""
109
+ return datetime.datetime.fromisoformat(val.decode())
110
+
111
+
112
+ def convert_timestamp(val):
113
+ """Convert Unix epoch timestamp to datetime.datetime object."""
114
+ return datetime.datetime.fromtimestamp(int(val))
115
+
116
+
117
+ sqlite3.register_converter("date", convert_date)
118
+ sqlite3.register_converter("datetime", convert_datetime)
119
+ sqlite3.register_converter("timestamp", convert_timestamp)
120
+ sqlite3.register_adapter(datetime.date, adapt_date_iso)
121
+ sqlite3.register_adapter(datetime.datetime, adapt_datetime_iso)
122
+ sqlite3.register_adapter(datetime.datetime, adapt_datetime_epoch)
sysview_py/flock.py ADDED
@@ -0,0 +1,13 @@
1
+ import fcntl
2
+ from contextlib import contextmanager
3
+
4
+
5
+ @contextmanager
6
+ def lock_file(file_path):
7
+ f = open(file_path, 'w')
8
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
9
+ try:
10
+ yield f
11
+ finally:
12
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
13
+ f.close()
sysview_py/main.py ADDED
@@ -0,0 +1,27 @@
1
+ from pathlib import Path
2
+ import sys
3
+
4
+ from .sysview import Sysview
5
+ from .flock import lock_file
6
+
7
+ LOCK_FILE=Path.home() / ".sysview.lock"
8
+
9
+
10
+ def main():
11
+ with lock_file(LOCK_FILE):
12
+ sysview = Sysview()
13
+ db_path = Path.home() / ".sysview.db"
14
+ sysview.setup_db(db_path)
15
+ reports = []
16
+ if not sys.stdin.isatty():
17
+ reports = sysview.read_raw_reports_from_stdin()
18
+ if reports:
19
+ sysview.process_reports(reports)
20
+ sysview.read_hosts()
21
+ sysview.get_latest_reports()
22
+ sysview.update_html()
23
+ print("finished")
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
sysview_py/report.py ADDED
@@ -0,0 +1,185 @@
1
+ import hashlib
2
+ from datetime import datetime
3
+
4
+
5
+ status_map = {
6
+ "UNKNOWN": 3,
7
+ "CRITICAL": 2,
8
+ "WARNING": 1,
9
+ "OK": 0
10
+ }
11
+
12
+ class Host:
13
+ def __init__(self, name, report_types=tuple(), reports=[]):
14
+ self.name = name
15
+ self.report_types = report_types
16
+ self.reports = reports
17
+
18
+ @property
19
+ def last_updated(self):
20
+ last_updated = None
21
+ for report in self.reports:
22
+ if last_updated is None:
23
+ last_updated = report.date
24
+ elif report.date > last_updated:
25
+ last_updated = report.date
26
+ return last_updated
27
+
28
+ @property
29
+ def all_items(self):
30
+ items = []
31
+ for report in self.reports:
32
+ for item in report.items:
33
+ item.report_type = report.type
34
+ item.date = report.date
35
+ item.host = self.name
36
+ items += report.items
37
+ items.sort(key=lambda item: item.status_code, reverse=True)
38
+ return items
39
+
40
+ @property
41
+ def status(self):
42
+ status = "UNKNOWN"
43
+ report_status = [report.status for report in self.reports]
44
+ if "OK" in report_status:
45
+ status = "OK"
46
+ if "UNKNOWN" in report_status:
47
+ status = "UNKNOWN"
48
+ if "WARNING" in report_status:
49
+ status = "WARNING"
50
+ if "CRITICAL" in report_status:
51
+ status = "CRITICAL"
52
+ return status
53
+
54
+ @property
55
+ def status_code(self):
56
+ return status_map[self.status]
57
+
58
+ def __repr__(self):
59
+ return f"Host(name={self.name}, status={self.status}, report_types={self.report_types})"
60
+
61
+
62
+ class ReportItem:
63
+ valid_statuses = ("OK", "WARNING", "CRITICAL", "UNKNOWN")
64
+
65
+ def __init__(
66
+ self,
67
+ raw_item,
68
+ command=None,
69
+ status="UNKNOWN",
70
+ status_text=None,
71
+ additional_text=None,
72
+ date=None,
73
+ host=None,
74
+ report_type=None,
75
+ report_id=None
76
+ ):
77
+ self.raw_item = raw_item
78
+ self.command = command
79
+ self._status = status
80
+ self.status_text = status_text
81
+ self.additional_text = additional_text
82
+ self.date = date
83
+ self.report_type = report_type
84
+ self.report_id = report_id
85
+ self.host = host
86
+
87
+ def __repr__(self):
88
+ return f"ReportItem(command={self.command}, status={self.status})"
89
+
90
+ @property
91
+ def status(self):
92
+ age = datetime.now() - self.date
93
+ status = self._status
94
+ if age > 1:
95
+ status = "OUTDATED"
96
+ return status
97
+
98
+ @property
99
+ def status_code(self):
100
+ return status_map[self.status]
101
+
102
+ @property
103
+ def id(self):
104
+ return hashlib.sha256((f"{self.report_id}_{self.command}").encode('utf-8')).hexdigest()
105
+
106
+ def parse(self):
107
+ parsed_required_fields = False
108
+ self.command = None
109
+ self.status = "UNKNOWN"
110
+ self.status_text = None
111
+ self.additional_text = None
112
+ for line in self.raw_item.split("\n"):
113
+ if None not in (self.command, self.status, self.status_text):
114
+ parsed_required_fields = True
115
+ if line.startswith("Command:") and not parsed_required_fields:
116
+ self.command = ":".join(line.split(":")[1:]).lstrip()
117
+ elif (
118
+ line.split(":")[0] in self.valid_statuses and not parsed_required_fields
119
+ ):
120
+ self.status = line.split(":")[0]
121
+ self.status_text = ":".join(line.split(":")[1:])
122
+ elif parsed_required_fields:
123
+ if self.additional_text is None:
124
+ self.additional_text = ""
125
+ self.additional_text += f"{line}\n"
126
+
127
+
128
+ class Report:
129
+ def __init__(
130
+ self, raw_report, hostname=None, date=None, report_type=None, items=None, report_id=None
131
+ ):
132
+ self.raw_report = raw_report
133
+ self.hostname = hostname
134
+ self.date = date
135
+ self.type = report_type
136
+ self.items = items
137
+ self.report_id = report_id
138
+
139
+ def __repr__(self):
140
+ return f"Report(hostname={self.hostname}, type={self.type}, date={self.date}, status={self.status})"
141
+
142
+ @property
143
+ def status(self):
144
+ status = "UNKNOWN"
145
+ item_status = [item.status for item in self.items]
146
+ if "OK" in item_status:
147
+ status = "OK"
148
+ if "WARNING" in item_status:
149
+ status = "WARNING"
150
+ if "CRITICAL" in item_status:
151
+ status = "CRITICAL"
152
+ return status
153
+
154
+ @property
155
+ def status_code(self):
156
+ return status_map[self.status]
157
+
158
+ def parse(self):
159
+ item_pointer = 0
160
+ raw_report = []
161
+ self.items = []
162
+ raw_item = ""
163
+ for line in self.raw_report.split("\n"):
164
+ raw_report.append(line)
165
+ if line.startswith("Hostname:") and item_pointer == 0:
166
+ self.hostname = line.split(":")[1].strip()
167
+ elif line.startswith("Date:") and item_pointer == 0:
168
+ raw_date = line.replace("Date:", "").strip()
169
+ self.date = datetime.strptime(raw_date, "%a %b %d %X %Z %Y")
170
+ elif line.startswith("Type:") and item_pointer == 0:
171
+ self.type = "".join(line.split(":")[1].strip())
172
+ elif item_pointer > 0 and line != "---":
173
+ raw_item += f"{line}\n"
174
+ elif "---" == line:
175
+ if item_pointer > 0:
176
+ item = ReportItem(raw_item, report_id=self.report_id)
177
+ item.parse()
178
+ self.items.append(item)
179
+ item_pointer = item_pointer + 1
180
+ raw_item = ""
181
+ item = ReportItem(raw_item, report_id=self.report_id)
182
+ item.parse()
183
+ self.items.append(item)
184
+ self.items.sort(key=lambda item: item.status_code, reverse=True)
185
+ self.raw_report = "\n".join(raw_report)