2022-01-16 14:42:56 +00:00
import subprocess
import sys
2022-01-18 22:18:39 +00:00
import argparse
import time
2022-01-19 21:15:29 +00:00
from datetime import datetime
2022-01-16 14:42:56 +00:00
class Chart ( object ) :
def __setattr__ ( self , name , value ) :
if name == ' status ' :
value = value . casefold ( )
if ' update_available ' in name :
value = True if value . casefold ( ) == " true " else False
super ( Chart , self ) . __setattr__ ( name , value )
def new_attr ( self , attr ) :
setattr ( self , attr , attr )
INSTALLED_CHARTS = [ ]
def parse_headers ( charts : str ) :
for line in charts . split ( " \n " ) :
if line . startswith ( " +- " ) :
continue
if " name " in line . casefold ( ) :
return [ col . strip ( ) for col in line . casefold ( ) . strip ( ) . split ( " | " ) if col and col != " " ]
def parse_charts ( charts : str ) :
headers = parse_headers ( charts )
table_data = charts . split ( " \n " ) [ 3 : - 2 : ] # Skip the header part of the table
for row in table_data :
row = [ section . strip ( ) for section in row . split ( " | " ) if section and section != " " ]
chart = Chart ( )
for item in zip ( headers , row ) :
setattr ( chart , item [ 0 ] , item [ 1 ] )
INSTALLED_CHARTS . append ( chart )
def check_semver ( current : str , latest : str ) :
split_current_semver = current . split ( " . " , 3 )
split_latest_semver = latest . split ( " . " , 3 )
if split_current_semver [ 0 ] != split_latest_semver [ 0 ] :
type = " major "
2022-01-18 22:18:39 +00:00
if VERSIONING == " major " :
2022-01-16 14:42:56 +00:00
return True
if split_current_semver [ 1 ] != split_latest_semver [ 1 ] :
type = " minor "
2022-01-18 22:18:39 +00:00
if VERSIONING != " patch " :
2022-01-16 14:42:56 +00:00
return True
if split_current_semver [ 2 ] != split_latest_semver [ 2 ] :
type = " patch "
return True
return False
def execute_upgrades ( ) :
2022-01-19 21:15:29 +00:00
if UPDATE :
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 )
2022-01-19 19:41:43 +00:00
else :
2022-01-19 21:15:29 +00:00
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 :
2022-01-18 22:57:15 +00:00
pre_update_ver = chart . human_version
post_update_ver = chart . human_latest_version
2022-01-19 21:15:29 +00:00
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... " )
2022-01-16 14:42:56 +00:00
def fetch_charts ( ) :
rawcharts = subprocess . run ( [ " cli " , " -c " , " app chart_release query " ] , stdout = subprocess . PIPE )
charts = rawcharts . stdout . decode ( ' utf-8 ' )
return ( charts )
2022-01-18 22:18:39 +00:00
def process_args ( ) :
global CATALOG
2022-01-19 17:34:20 +00:00
global VERSIONING
2022-01-18 22:18:39 +00:00
global SYNC
2022-01-19 16:37:14 +00:00
global PRUNE
2022-01-19 19:46:10 +00:00
global ALL
2022-01-19 20:00:14 +00:00
global BACKUP
2022-01-19 21:15:29 +00:00
global UPDATE
global RESTORE
global LIST
2022-01-20 14:45:13 +00:00
global DELETE
2022-01-18 22:18:39 +00:00
parser = argparse . ArgumentParser ( description = ' Update TrueNAS SCALE Apps ' )
2022-01-19 21:15:29 +00:00
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 ' )
2022-01-20 14:45:13 +00:00
parser . add_argument ( ' -b ' , ' --backup ' , nargs = ' ? ' , const = ' 14 ' , help = ' backup the complete Apps system prior to updates, add a number to specify the max old backups to keep ' )
parser . add_argument ( ' -r ' , ' --restore ' , nargs = ' ? ' , help = ' restore a previous backup, disables all other features ' )
parser . add_argument ( ' -d ' , ' --delete ' , nargs = ' ? ' , help = ' delete a specific backup ' )
2022-01-18 22:18:39 +00:00
parser . add_argument ( ' -s ' , ' --sync ' , action = " store_true " , help = ' sync catalogs before trying to update ' )
2022-01-19 21:15:29 +00:00
parser . add_argument ( ' -u ' , ' --update ' , action = " store_true " , help = ' update the Apps in the selected catalog ' )
2022-01-19 16:37:14 +00:00
parser . add_argument ( ' -p ' , ' --prune ' , action = " store_true " , help = ' prune old docker images after update ' )
2022-01-19 19:46:10 +00:00
parser . add_argument ( ' -a ' , ' --all ' , action = " store_true " , help = ' update " active " apps only and ignore " stopped " or " stuck " apps ' )
2022-01-19 21:15:29 +00:00
parser . add_argument ( ' -l ' , ' --list ' , action = " store_true " , help = ' lists existing backups ' )
2022-01-18 22:18:39 +00:00
args = parser . parse_args ( )
CATALOG = args . catalog
VERSIONING = args . versioning
2022-01-19 21:15:29 +00:00
RESTORE = args . restore
2022-01-20 14:45:13 +00:00
BACKUP = args . backup
DELETE = args . delete
2022-01-19 21:15:29 +00:00
if args . update :
UPDATE = True
else :
UPDATE = False
2022-01-18 22:18:39 +00:00
if args . sync :
SYNC = True
2022-01-18 23:03:23 +00:00
else :
SYNC = False
2022-01-19 16:37:14 +00:00
if args . prune :
PRUNE = True
else :
PRUNE = False
2022-01-19 19:46:10 +00:00
if args . all :
ALL = True
2022-01-19 19:41:43 +00:00
else :
2022-01-19 19:46:10 +00:00
ALL = False
2022-01-19 21:15:29 +00:00
if args . list :
LIST = True
else :
LIST = False
2022-01-18 22:18:39 +00:00
def sync_catalog ( ) :
if SYNC :
print ( " Syncing Catalogs... \n " )
process = subprocess . Popen ( [ " cli " , " -c " , " app catalog sync_all " ] , stdout = subprocess . PIPE )
while process . poll ( ) is None :
lines = process . stdout . readline ( )
2022-01-19 19:34:56 +00:00
print ( lines . decode ( ' utf-8 ' ) )
temp = process . stdout . read ( )
if temp :
print ( temp . decode ( ' utf-8 ' ) )
2022-01-19 21:15:29 +00:00
else :
print ( " Catalog Sync disabled, skipping... " )
2022-01-18 22:18:39 +00:00
2022-01-19 16:37:14 +00:00
def docker_prune ( ) :
if PRUNE :
print ( " Pruning old docker images... \n " )
2022-01-20 14:45:13 +00:00
process = subprocess . run ( [ " docker " , " image " , " prune " , " -af " ] , stdout = subprocess . PIPE )
2022-01-19 18:12:29 +00:00
print ( " Images pruned. \n " )
2022-01-19 21:15:29 +00:00
else :
print ( " Container Image Pruning disabled, skipping... " )
2022-01-19 20:00:14 +00:00
2022-01-19 21:15:29 +00:00
def apps_backup ( ) :
2022-01-19 20:00:14 +00:00
if BACKUP :
2022-01-20 14:45:13 +00:00
print ( f " Cleaning old backups to a max. of { BACKUP } ... \n " )
backups_fetch = get_backups_names ( )
backups_cleaned = [ k for k in backups_fetch if ' TrueTool ' in k ]
backups_remove = backups_cleaned [ : len ( backups_cleaned ) - int ( BACKUP ) ]
for backup in backups_remove :
backups_delete ( backup )
2022-01-19 20:00:14 +00:00
print ( " Running App Backup... \n " )
2022-01-19 21:15:29 +00:00
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 )
2022-01-19 20:00:14 +00:00
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 ' ) )
2022-01-19 21:15:29 +00:00
else :
print ( " Backup disabled, skipping... " )
def backups_list ( ) :
if LIST :
2022-01-20 14:45:13 +00:00
print ( " Generating Backup list... \n " )
backups = get_backups_names ( )
for backup in backups :
print ( f " { backup } " )
def backups_delete ( backup : str ) :
print ( f " removing { backup } ... " )
process = subprocess . run ( [ " midclt " , " call " , " kubernetes.delete_backup " , backup ] , stdout = subprocess . PIPE )
def get_backups_names ( ) :
names = [ ]
process = subprocess . run ( [ " cli " , " -c " , " app kubernetes list_backups " ] , stdout = subprocess . PIPE )
output = process . stdout . decode ( ' utf-8 ' )
for line in output . split ( " \n " ) :
if line . startswith ( " +- " ) :
continue
else :
rowlist = [ col . strip ( ) for col in line . strip ( ) . split ( " | " ) if col and col != " " ]
if rowlist :
names . append ( rowlist [ 0 ] )
names . sort ( )
return names
2022-01-19 21:15:29 +00:00
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 ' ) )
2022-01-19 16:37:14 +00:00
2022-01-16 15:07:36 +00:00
def run ( ) :
2022-01-18 22:18:39 +00:00
process_args ( )
2022-01-19 21:15:29 +00:00
print ( " Starting TrueCharts TrueTool... \n " )
if RESTORE :
apps_restore ( )
elif LIST :
backups_list ( )
2022-01-20 14:45:13 +00:00
elif DELETE :
backups_delete ( DELETE )
2022-01-19 21:15:29 +00:00
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 )
2022-01-18 22:18:39 +00:00
2022-01-16 14:42:56 +00:00
if __name__ == ' __main__ ' :
2022-01-16 14:59:43 +00:00
run ( )