-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add network_not_metered and custom check scripts (#111)
- Loading branch information
Showing
6 changed files
with
299 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,58 @@ | ||
import tomllib | ||
import os | ||
from typing import List, Optional | ||
from logging import getLogger | ||
|
||
"""Setup logging""" | ||
log = getLogger(__name__) | ||
|
||
def load_config(): | ||
|
||
def find_default_config_file(): | ||
# load config values | ||
config_paths = [ | ||
"/etc/ublue-update/ublue-update.toml", | ||
"/usr/etc/ublue-update/ublue-update.toml", | ||
] | ||
|
||
# search for the right config | ||
config_path = "" | ||
fallback_config_path = "" | ||
# first config file that is found wins | ||
for path in config_paths: | ||
if os.path.isfile(path): | ||
if config_path == "": | ||
config_path = path | ||
fallback_config_path = path | ||
break | ||
return path | ||
|
||
return None | ||
|
||
|
||
def load_value(dct, *keys): | ||
for key in keys: | ||
try: | ||
dct = dct[key] | ||
except KeyError: | ||
return None | ||
return dct | ||
|
||
|
||
fallback_config = tomllib.load(open(fallback_config_path, "rb")) | ||
config = tomllib.load(open(config_path, "rb")) | ||
return config, fallback_config | ||
class Config: | ||
dbus_notify: bool | ||
network_not_metered: Optional[bool] | ||
min_battery_percent: Optional[float] | ||
max_cpu_load_percent: Optional[float] | ||
max_mem_percent: Optional[float] | ||
custom_check_scripts: List[dict] | ||
|
||
def load_config(self, path=None): | ||
config_path = path or find_default_config_file() | ||
config = tomllib.load(open(config_path, "rb")) | ||
log.debug(f"Configuration loaded from {os.path.abspath(config_path)}") | ||
self.load_values(config) | ||
|
||
def load_value(key, value): | ||
fallback = fallback_config[key][value] | ||
if key in config.keys(): | ||
return config[key].get(value, fallback) | ||
return fallback | ||
def load_values(self, config): | ||
self.dbus_notify = load_value(config, "notify", "dbus_notify") or False | ||
self.network_not_metered = load_value(config, "checks", "network_not_metered") | ||
self.min_battery_percent = load_value(config, "checks", "min_battery_percent") | ||
self.max_cpu_load_percent = load_value(config, "checks", "max_cpu_load_percent") | ||
self.max_mem_percent = load_value(config, "checks", "max_mem_percent") | ||
self.custom_check_scripts = load_value(config, "checks", "scripts") or [] | ||
|
||
|
||
config, fallback_config = load_config() | ||
cfg = Config() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import psutil | ||
import subprocess | ||
from typing import List, Optional | ||
from logging import getLogger | ||
from ublue_update.config import cfg | ||
|
||
"""Setup logging""" | ||
log = getLogger(__name__) | ||
|
||
|
||
def run_custom_check_script(script) -> dict: | ||
if 'run' in script and 'shell' not in script: | ||
raise Exception('checks.scripts.*: \'shell\' must be specified when \'run\' is used') | ||
|
||
if 'run' in script and 'file' in script: | ||
raise Exception('checks.scripts.*: Only one of \'run\' and \'file\' must be set for a given script') | ||
|
||
log.debug(f"Running script {script}") | ||
|
||
# Run the specified custom script | ||
if 'run' in script: | ||
run_args = [script['shell'], '-c', script['run']] | ||
elif 'shell' in script: | ||
run_args = [script['shell'], script['file']] | ||
else: | ||
run_args = [script['file']] | ||
script_result = subprocess.run(run_args, capture_output=True, text=True, check=False) | ||
|
||
# An exit code of 0 means "OK", a non-zero exit code | ||
# means "Do not download or perform updates right now" | ||
script_pass: bool = script_result.returncode == 0 | ||
|
||
# Use either the message specified in the config, | ||
# the output of the script (if not empty), or a fallback | ||
script_output: Optional[str] = script_result.stdout.strip() | ||
if len(script_output) == 0: | ||
script_output = None | ||
|
||
# Write error messages to our log in case of failure | ||
# to catch any interpreter errors etc. | ||
script_stderr = script_result.stderr.strip() | ||
if not script_pass and len(script_stderr) > 0: | ||
log.warning(f"A custom check script failed and wrote the following to STDERR:\n====\n{script_stderr}\n====") | ||
|
||
fallback_message = "A custom check script returned a non-0 exit code" | ||
script_message = script.get('message') or script_output or fallback_message | ||
|
||
return { | ||
"passed": script_pass, | ||
"message": script_message, | ||
} | ||
|
||
|
||
def run_custom_check_scripts() -> List[dict]: | ||
results = [] | ||
for script in (cfg.custom_check_scripts or []): | ||
results.append(run_custom_check_script(script)) | ||
return results | ||
|
||
|
||
def check_custom_inhibitors() -> bool: | ||
|
||
custom_inhibitors = run_custom_check_scripts() | ||
|
||
failures = [] | ||
custom_checks_failed = False | ||
for inhibitor_result in custom_inhibitors: | ||
if not inhibitor_result["passed"]: | ||
custom_checks_failed = True | ||
failures.append(inhibitor_result["message"]) | ||
if not custom_checks_failed: | ||
log.info("System passed custom checks") | ||
return custom_checks_failed, failures |
Oops, something went wrong.