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-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 19:46:10 +00:00
if ALL :
2022-01-19 19:41:43 +00:00
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-16 14:42:56 +00:00
else :
2022-01-19 19:41:43 +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 )
2022-01-16 14:42:56 +00:00
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 ) :
2022-01-18 22:18:39 +00:00
print ( f " Updating { chart . name } ... \n " )
2022-01-18 22:57:15 +00:00
pre_update_ver = chart . human_version
2022-01-19 16:59:00 +00:00
result = subprocess . run ( [ ' cli ' , ' -c ' , f ' app chart_release upgrade release_name= " { chart . name } " ' ] , capture_output = True )
2022-01-18 22:57:15 +00:00
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 } ) " )
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-18 22:18:39 +00:00
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 ( ' -s ' , ' --sync ' , action = " store_true " , help = ' sync catalogs before trying to update ' )
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 20:00:14 +00:00
parser . add_argument ( ' -b ' , ' --backup ' , action = " store_true " , help = ' backup the complete Apps system prior to updates ' )
2022-01-18 22:18:39 +00:00
args = parser . parse_args ( )
CATALOG = args . catalog
VERSIONING = args . versioning
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 20:00:14 +00:00
if args . backup :
BACKUP = True
else :
BACKUP = 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-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-19 17:59:44 +00:00
process = subprocess . Popen ( [ " docker " , " image " , " prune " , " -af " ] , stdout = subprocess . PIPE )
2022-01-19 18:12:29 +00:00
print ( " Images pruned. \n " )
2022-01-19 20:00:14 +00:00
def chart_backup ( ) :
if BACKUP :
print ( " Running App Backup... \n " )
process = subprocess . Popen ( [ " cli " , " -c " , " app kubernetes backup_chart_releases " ] , 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 ( )
print ( " Starting TrueCharts App updater... \n " )
2022-01-19 20:00:14 +00:00
chart_backup ( )
2022-01-18 22:18:39 +00:00
sync_catalog ( )
2022-01-16 14:59:43 +00:00
charts = fetch_charts ( )
parse_charts ( charts )
2022-01-18 22:18:39 +00:00
print ( " Executing Updates... \n " )
2022-01-16 14:59:43 +00:00
execute_upgrades ( )
2022-01-18 22:18:39 +00:00
print ( " Updating Finished \n " )
2022-01-19 17:32:38 +00:00
docker_prune ( )
2022-01-18 22:18:39 +00:00
exit ( 0 )
2022-01-16 14:42:56 +00:00
if __name__ == ' __main__ ' :
2022-01-16 14:59:43 +00:00
run ( )