From fdd76bbce595e7180785e3c069e1dca5630aa743 Mon Sep 17 00:00:00 2001 From: kjeld Schouten-Lebbing Date: Wed, 19 Jan 2022 22:15:29 +0100 Subject: [PATCH] add basic backup, backup-list and restore functionality --- README.md | 28 ++++++--- setup.py | 2 +- truetool/__init__.py | 135 ++++++++++++++++++++++++++++++------------- 3 files changed, 118 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 281d5f99..8c4e5a9f 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,30 @@ run `pip install --upgrade truetool` ## How to use -Just run `truetool` in the shell of your TrueNAS SCALE machine, to have it process Patch and Minor version updates for all Apps +running `truetool` should be a good start. Additional options are available: -- `truetool --catalog CATALOGNAME` where CATALOGNAME is the name of the catalog you want to process in caps -- `truetool --versioning SCHEME` where SCHEME is the highest semver version you want to process. options: `patch`, `minor` and `major` - +##### Help - `truetool -h` for the CLI help page -- `truetool -s` or ` truetool --sync` to sync the catalogs before running auto-update -- `truetool -p` or ` truetool --prune` to prune (remove) old docker images after running auto-update + + +##### Update + +- `truetool -u` or ` truetool --update` update TrueNAS SCALE Apps + + +- `truetool --catalog CATALOGNAME` where CATALOGNAME is the name of the catalog you want to process in caps +- `truetool --versioning SCHEME` where SCHEME is the highest semver version you want to process. options: `patch`, `minor` and `major` - `truetool -a` or ` truetool --all` updates both active (running) and non-active (stuck or stopped) Apps -- `truetool -b` or ` truetool --backup` backup the complete Apps system prior to updates \ No newline at end of file + + +#### Backup +- `truetool -b` or ` truetool --backup` backup the complete Apps system prior to updates +- `truetool -r` or ` truetool --restore` restores a specific backup by name + +#### Other + +- `truetool -s` or ` truetool --sync` to sync the catalogs before running updates +- `truetool -p` or ` truetool --prune` to prune (remove) old docker images after running auto-update \ No newline at end of file diff --git a/setup.py b/setup.py index 8c8904e6..1d6606e1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ README_MD = open(join(dirname(abspath(__file__)), "README.md")).read() setup( name="truetool", - version="1.0.0", + version="2.0.0", # The packages that constitute your project. # For my project, I have only one - "pydash". diff --git a/truetool/__init__.py b/truetool/__init__.py index c75ccebf..2c2891fe 100644 --- a/truetool/__init__.py +++ b/truetool/__init__.py @@ -2,6 +2,7 @@ import subprocess import sys import argparse import time +from datetime import datetime class Chart(object): def __setattr__(self, name, value): @@ -51,32 +52,35 @@ def check_semver(current: str, latest: str): def execute_upgrades(): - if ALL: - if CATALOG == "ALL": - filtered = filter(lambda a: a.update_available and a.status == "active", INSTALLED_CHARTS) - else: - filtered = filter(lambda a: a.update_available and a.status == "active" and a.catalog == CATALOG, INSTALLED_CHARTS) - else: - if CATALOG == "ALL": - filtered = filter(lambda a: a.update_available, INSTALLED_CHARTS) - else: - filtered = filter(lambda a: a.update_available and a.catalog == CATALOG, INSTALLED_CHARTS) - for chart in filtered: - pre_update_ver = chart.human_version - post_update_ver = chart.human_latest_version - split_current_version = chart.human_version.split("_", 1) - current_version = split_current_version[1] - split_latest = chart.human_latest_version.split("_", 1) - latest_version = split_latest[1] - if check_semver(current_version, latest_version): - print(f"Updating {chart.name}... \n") - pre_update_ver = chart.human_version - result = subprocess.run(['cli', '-c', f'app chart_release upgrade release_name="{chart.name}"'], capture_output=True) - post_update_ver = chart.human_latest_version - if "Upgrade complete" not in result.stdout.decode('utf-8'): - print(f"{chart.name} failed to upgrade. \n{result.stdout.decode('utf-8')}") + if UPDATE: + if ALL: + if CATALOG == "ALL": + filtered = filter(lambda a: a.update_available and a.status == "active", INSTALLED_CHARTS) else: - print(f"{chart.name} upgraded ({pre_update_ver} --> {post_update_ver})") + filtered = filter(lambda a: a.update_available and a.status == "active" and a.catalog == CATALOG, INSTALLED_CHARTS) + else: + if CATALOG == "ALL": + filtered = filter(lambda a: a.update_available, INSTALLED_CHARTS) + else: + filtered = filter(lambda a: a.update_available and a.catalog == CATALOG, INSTALLED_CHARTS) + for chart in filtered: + pre_update_ver = chart.human_version + post_update_ver = chart.human_latest_version + split_current_version = chart.human_version.split("_", 1) + current_version = split_current_version[1] + split_latest = chart.human_latest_version.split("_", 1) + latest_version = split_latest[1] + if check_semver(current_version, latest_version): + print(f"Updating {chart.name}... \n") + pre_update_ver = chart.human_version + result = subprocess.run(['cli', '-c', f'app chart_release upgrade release_name="{chart.name}"'], capture_output=True) + post_update_ver = chart.human_latest_version + if "Upgrade complete" not in result.stdout.decode('utf-8'): + print(f"{chart.name} failed to upgrade. \n{result.stdout.decode('utf-8')}") + else: + print(f"{chart.name} upgraded ({pre_update_ver} --> {post_update_ver})") + else: + print("Update disabled, skipping...") def fetch_charts(): rawcharts = subprocess.run(["cli", "-c", "app chart_release query"], stdout=subprocess.PIPE) @@ -90,16 +94,27 @@ def process_args(): global PRUNE global ALL global BACKUP + global UPDATE + global RESTORE + global LIST parser = argparse.ArgumentParser(description='Update TrueNAS SCALE Apps') - parser.add_argument('--catalog', nargs='?', default='ALL', help='name of the catalog you want to process in caps. Or "ALL" to render all catalogs.') - parser.add_argument('--versioning', nargs='?', default='minor', help='Name of the versioning scheme you want to update. Options: major, minor or patch. Defaults to minor') + parser.add_argument('-c', '--catalog', nargs='?', default='ALL', help='name of the catalog you want to process in caps. Or "ALL" to render all catalogs.') + parser.add_argument('-v', '--versioning', nargs='?', default='minor', help='Name of the versioning scheme you want to update. Options: major, minor or patch. Defaults to minor') parser.add_argument('-s', '--sync', action="store_true", help='sync catalogs before trying to update') + parser.add_argument('-u', '--update', action="store_true", help='update the Apps in the selected catalog') parser.add_argument('-p', '--prune', action="store_true", help='prune old docker images after update') parser.add_argument('-a', '--all', action="store_true", help='update "active" apps only and ignore "stopped" or "stuck" apps') parser.add_argument('-b', '--backup', action="store_true", help='backup the complete Apps system prior to updates') + parser.add_argument('-r', '--restore', nargs='?', help='restore a previous backup, disables all other features') + parser.add_argument('-l', '--list', action="store_true", help='lists existing backups') args = parser.parse_args() CATALOG = args.catalog VERSIONING = args.versioning + RESTORE = args.restore + if args.update: + UPDATE = True + else: + UPDATE = False if args.sync: SYNC = True else: @@ -116,6 +131,11 @@ def process_args(): BACKUP = True else: BACKUP = False + if args.list: + LIST = True + else: + LIST = False + def sync_catalog(): if SYNC: @@ -127,36 +147,73 @@ def sync_catalog(): temp = process.stdout.read() if temp: print (temp.decode('utf-8')) + else: + print("Catalog Sync disabled, skipping...") def docker_prune(): if PRUNE: print("Pruning old docker images...\n") process = subprocess.Popen(["docker", "image", "prune", "-af"], stdout=subprocess.PIPE) print("Images pruned.\n") + else: + print("Container Image Pruning disabled, skipping...") -def chart_backup(): +def apps_backup(): if BACKUP: print("Running App Backup...\n") - process = subprocess.Popen(["cli", "-c", "app kubernetes backup_chart_releases"], stdout=subprocess.PIPE) + now = datetime.now() + command = "app kubernetes backup_chart_releases backup_name=TrueTool_"+now.strftime("%Y_%d_%m_%H_%M_%S") + process = subprocess.Popen(["cli", "-c", command], stdout=subprocess.PIPE) while process.poll() is None: lines = process.stdout.readline() print (lines.decode('utf-8')) temp = process.stdout.read() if temp: print (temp.decode('utf-8')) + else: + print("Backup disabled, skipping...") + +def backups_list(): + if LIST: + print("Running App Backup...\n") + process = subprocess.Popen(["cli", "-c", "app kubernetes list_backups"], stdout=subprocess.PIPE) + while process.poll() is None: + lines = process.stdout.readline() + print (lines.decode('utf-8')) + temp = process.stdout.read() + if temp: + print (temp.decode('utf-8')) + +def apps_restore(): + print("Running Backup Restore...\n") + command = "app kubernetes restore_backup backup_name="+RESTORE + print(f"{command}") + process = subprocess.Popen(["cli", "-c", command], stdout=subprocess.PIPE) + while process.poll() is None: + lines = process.stdout.readline() + print (lines.decode('utf-8')) + temp = process.stdout.read() + if temp: + print (temp.decode('utf-8')) + def run(): process_args() - print("Starting TrueCharts App updater...\n") - chart_backup() - sync_catalog() - charts = fetch_charts() - parse_charts(charts) - print("Executing Updates...\n") - execute_upgrades() - print("Updating Finished\n") - docker_prune() - exit(0) + print("Starting TrueCharts TrueTool...\n") + if RESTORE: + apps_restore() + elif LIST: + backups_list() + else: + apps_backup() + sync_catalog() + charts = fetch_charts() + parse_charts(charts) + print("Executing Updates...\n") + execute_upgrades() + docker_prune() + print("TrueTool Finished\n") + exit(0) if __name__ == '__main__':