diff --git a/linupdate.py b/linupdate.py index d0073de..fa09cfb 100755 --- a/linupdate.py +++ b/linupdate.py @@ -101,7 +101,7 @@ def main(): # Execute packages update my_package.update(my_args.packages_to_update, my_args.assume_yes, - my_args.ignore_exclude, + my_args.ignore_exclusions, my_args.check_updates, my_args.dist_upgrade, my_args.keep_oldconf, diff --git a/src/controllers/App/Utils.py b/src/controllers/App/Utils.py index 14feafb..4d9f1c5 100644 --- a/src/controllers/App/Utils.py +++ b/src/controllers/App/Utils.py @@ -49,3 +49,14 @@ def clean_log(self, text): text = text.strip() return text + + #----------------------------------------------------------------------------------------------- + # + # Convert a string to a boolean + # + #----------------------------------------------------------------------------------------------- + def stringToBoolean(self, value: str): + if value == 'true' or value == 'True': + return True + + return False diff --git a/src/controllers/Args.py b/src/controllers/Args.py index 1e4803c..826622e 100644 --- a/src/controllers/Args.py +++ b/src/controllers/Args.py @@ -40,7 +40,7 @@ def parse(self): # Default values Args.assume_yes = False Args.check_updates = False - Args.ignore_exclude = False + Args.ignore_exclusions = False Args.packages_to_update = [] Args.dist_upgrade = False Args.keep_oldconf = True @@ -95,7 +95,7 @@ def parse(self): # Check updates parser.add_argument("--check-updates", "-cu", action="store_true", default="null") # Ignore exclude - parser.add_argument("--ignore-exclude", "-ie", action="store_true", default="null") + parser.add_argument("--ignore-exclusions", "-ie", action="store_true", default="null") # Exit on package update error parser.add_argument("--exit-on-package-update-error", action="store", nargs='?', default="null") @@ -367,10 +367,10 @@ def parse(self): Args.dry_run = True # - # If --ignore-exclude param has been set + # If --ignore-exclusions param has been set # - if args.ignore_exclude != "null": - Args.ignore_exclude = True + if args.ignore_exclusions != "null": + Args.ignore_exclusions = True # # If --check-updates param has been set @@ -742,7 +742,7 @@ def help(self): }, { 'args': [ - '--ignore-exclude', + '--ignore-exclusions', '-ie' ], 'description': 'Ignore all package exclusions' diff --git a/src/controllers/Module/Reposerver/Agent.py b/src/controllers/Module/Reposerver/Agent.py index 75683ec..69d0cf8 100644 --- a/src/controllers/Module/Reposerver/Agent.py +++ b/src/controllers/Module/Reposerver/Agent.py @@ -259,6 +259,12 @@ def websocket_on_message(self, ws, message): status = None error = None + # Update parameters default values + dry_run = False + ignore_exclusions = False + keep_oldconf = True + full_upgrade = False + # Decode JSON message message = json.loads(message) @@ -327,42 +333,63 @@ def websocket_on_message(self, ws, message): with Log(log): self.reposerverStatusController.send_packages_info() - # Case the request is 'update-all-packages', then update all packages - elif message['request'] == 'update-all-packages': + # Case the request is 'request-all-packages-update', then update all packages + elif message['request'] == 'request-all-packages-update': print('[reposerver-agent] Reposerver requested all packages update') + # Try to retrieve the update params + if 'data' in message: + if 'update-params' in message['data']: + if 'dry-run' in message['data']['update-params']: + dry_run = Utils().stringToBoolean(message['data']['update-params']['dry-run']) + if 'ignore-exclusions' in message['data']['update-params']: + ignore_exclusions = Utils().stringToBoolean(message['data']['update-params']['ignore-exclusions']) + if 'keep-config-files' in message['data']['update-params']: + keep_oldconf = Utils().stringToBoolean(message['data']['update-params']['keep-config-files']) + if 'full-upgrade' in message['data']['update-params']: + full_upgrade = Utils().stringToBoolean(message['data']['update-params']['full-upgrade']) + # Send a response to the reposerver to make the request as running self.set_request_status(request_id, 'running') # Log everything to the log file with Log(log): - self.packageController.update([], True) + self.packageController.update([], True, ignore_exclusions, False, full_upgrade, keep_oldconf, dry_run) # TODO - # Take into account the --dry-run option # Restart/reload services if the user said so # Execute reposever post-update actions # Send a summary to the reposerver, with the summary of the update (number of packages updated or failed) summary = self.packageController.summary - # Case the request is 'request-specific-packages-installation', then update a list of packages + # Case the request is 'request-packages-update', then update a list of specific packages # A list of packages must be provided in the message - elif message['request'] == 'request-specific-packages-installation': + elif message['request'] == 'request-packages-update': + print('[reposerver-agent] Reposerver requested to update a list of packages') + # Try to retrieve the update packages and params if 'data' in message: + if 'update-params' in message['data']: + if 'dry-run' in message['data']['update-params']: + dry_run = Utils().stringToBoolean(message['data']['update-params']['dry-run']) + if 'ignore-exclusions' in message['data']['update-params']: + ignore_exclusions = Utils().stringToBoolean(message['data']['update-params']['ignore-exclusions']) + if 'keep-config-files' in message['data']['update-params']: + keep_oldconf = Utils().stringToBoolean(message['data']['update-params']['keep-config-files']) + if 'full-upgrade' in message['data']['update-params']: + full_upgrade = Utils().stringToBoolean(message['data']['update-params']['full-upgrade']) + + # If a list of packages is provided, then the update can be performed if 'packages' in message['data'] and len(message['data']['packages']) > 0: - print('[reposerver-agent] Reposerver requested to update a list of packages') - # Send a response to the reposerver to make the request as running self.set_request_status(request_id, 'running') # Log everything to the log file with Log(log): - self.packageController.update(message['data']['packages'], True) + self.packageController.update(message['data']['packages'], True, ignore_exclusions, False, full_upgrade, keep_oldconf, dry_run) # Send a summary to the reposerver, with the summary of the installation (number of packages installed or failed) summary = self.packageController.summary - else: raise Exception('unknown request sent by reposerver: ' + message['request']) diff --git a/src/controllers/Module/Reposerver/Status.py b/src/controllers/Module/Reposerver/Status.py index eda99d0..e4e6143 100644 --- a/src/controllers/Module/Reposerver/Status.py +++ b/src/controllers/Module/Reposerver/Status.py @@ -58,7 +58,7 @@ def send_general_info(self): try: self.httpRequestController.quiet = False - self.httpRequestController.put(url + '/api/v2/host/status', id, token, data) + self.httpRequestController.put(url + '/api/v2/host/status', id, token, data, 5, 10) except Exception as e: raise Exception('error while sending general status to reposerver: ' + str(e)) @@ -103,7 +103,7 @@ def sendAvailablePackagesStatus(self): for package in packages: name = package['name'] - available_version = package['available_version'] + available_version = package['target_version'] # Ignore package if name is empty if name == '': @@ -134,7 +134,7 @@ def sendAvailablePackagesStatus(self): print(' Sending available packages to ' + Fore.YELLOW + url + Style.RESET_ALL + ':') self.httpRequestController.quiet = False - self.httpRequestController.put(url + '/api/v2/host/packages/available', id, token, data) + self.httpRequestController.put(url + '/api/v2/host/packages/available', id, token, data, 5, 10) #----------------------------------------------------------------------------------------------- @@ -184,7 +184,7 @@ def sendInstalledPackagesStatus(self): print(' Sending installed packages to ' + Fore.YELLOW + url + Style.RESET_ALL + ':') self.httpRequestController.quiet = False - self.httpRequestController.put(url + '/api/v2/host/packages/installed', id, token, data) + self.httpRequestController.put(url + '/api/v2/host/packages/installed', id, token, data, 5, 10) #----------------------------------------------------------------------------------------------- @@ -235,4 +235,4 @@ def send_packages_history(self, entries_limit: int = 999999): print(' Sending packages events to ' + Fore.YELLOW + url + Style.RESET_ALL + ':') self.httpRequestController.quiet = False - self.httpRequestController.put(url + '/api/v2/host/packages/event', id, token, events) + self.httpRequestController.put(url + '/api/v2/host/packages/event', id, token, events, 5, 10) diff --git a/src/controllers/Package/Apt.py b/src/controllers/Package/Apt.py index fa9a2cf..1852d36 100644 --- a/src/controllers/Package/Apt.py +++ b/src/controllers/Package/Apt.py @@ -128,7 +128,7 @@ def get_available_packages(self, dist_upgrade: bool = False): myPackage = { 'name': pkg.name, 'current_version': pkg.installed.version, - 'available_version': pkg.candidate.version + 'target_version': pkg.candidate.version } list.append(myPackage) @@ -301,7 +301,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru Path(log).unlink() with Log(log): - print('\n ▪ Updating ' + Fore.GREEN + pkg['name'] + Style.RESET_ALL + ' (' + pkg['current_version'] + ' → ' + pkg['available_version'] + '):') + print('\n ▪ Updating ' + Fore.GREEN + pkg['name'] + Style.RESET_ALL + ' (' + pkg['current_version'] + ' → ' + pkg['target_version'] + '):') # Before updating, check If package is already in the latest version, if so, skip it # It means that it has been updated previously by another package, probably because it was a dependency @@ -311,7 +311,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru temp_apt_cache.open(None) # If version in cache is the same the target version, skip the update - if temp_apt_cache[pkg['name']].installed.version == pkg['available_version']: + if temp_apt_cache[pkg['name']].installed.version == pkg['target_version']: print(Fore.GREEN + ' ✔ ' + Style.RESET_ALL + pkg['name'] + ' is already up to date (updated with another package).') # Mark the package as already updated @@ -319,7 +319,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Also add the package to the list of successful packages self.summary['update']['success']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': 'Already up to date (updated with another package).' } @@ -332,17 +332,20 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # If --keep-oldconf is True, then keep the old configuration file if self.keep_oldconf: cmd = [ - 'apt-get', 'install', pkg['name'] + '=' + pkg['available_version'], '-y', - # Debug only - # '--dry-run', + 'apt-get', 'install', pkg['name'] + '=' + pkg['target_version'], '-y', '-o', 'Dpkg::Options::=--force-confdef', - '-o', 'Dpkg::Options::=--force-confold' + '-o', 'Dpkg::Options::=--force-confold', + # Debug only + # '--dry-run' ] else: - cmd = ['apt-get', 'install', pkg['name'] + '=' + pkg['available_version'], '-y'] + cmd = ['apt-get', 'install', pkg['name'] + '=' + pkg['target_version'], '-y', + # Debug only + # '--dry-run' + ] # If --dry-run is True, then simulate the update - if dry_run: + if dry_run == True: cmd.append('--dry-run') popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) @@ -372,7 +375,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Add the package to the list of failed packages self.summary['update']['failed']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': log_content } @@ -393,7 +396,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Add the package to the list of successful packages self.summary['update']['success']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': log_content } diff --git a/src/controllers/Package/Dnf.py b/src/controllers/Package/Dnf.py index 3e9e69a..b3c328b 100644 --- a/src/controllers/Package/Dnf.py +++ b/src/controllers/Package/Dnf.py @@ -157,7 +157,7 @@ def get_available_packages(self, useless_dist_upgrade: bool = False): list.append({ 'name': package[0], 'current_version': current_version, - 'available_version': package[1], + 'target_version': package[1], 'repository': package[2] }) @@ -306,7 +306,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru Path(log).unlink() with Log(log): - print('\n ▪ Updating ' + Fore.GREEN + pkg['name'] + Style.RESET_ALL + ' (' + pkg['current_version'] + ' → ' + pkg['available_version'] + '):') + print('\n ▪ Updating ' + Fore.GREEN + pkg['name'] + Style.RESET_ALL + ' (' + pkg['current_version'] + ' → ' + pkg['target_version'] + '):') # Before updating, check if package is already in the latest version, if so, skip it # It means that it has been updated previously by another package, probably because it was a dependency @@ -327,7 +327,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru current_version = result.stdout.strip() # If current version is the same the target version, skip the update - if current_version == pkg['available_version']: + if current_version == pkg['target_version']: print(Fore.GREEN + ' ✔ ' + Style.RESET_ALL + pkg['name'] + ' is already up to date (updated with another package).') # Mark the package as already updated @@ -335,7 +335,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Also add the package to the list of successful packages self.summary['update']['success']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': 'Already up to date (updated with another package).' } @@ -343,10 +343,10 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru continue # Define the command to update the package - cmd = ['dnf', 'update', pkg['name'] + '-' + pkg['available_version'], '-y'] + cmd = ['dnf', 'update', pkg['name'] + '-' + pkg['target_version'], '-y'] # If dry_run is True, add the --setopt tsflags=test option to simulate the update - if dry_run: + if dry_run == True: cmd.append('--setopt') cmd.append('tsflags=test') @@ -371,7 +371,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Add the package to the list of failed packages self.summary['update']['failed']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': log_content } @@ -392,7 +392,7 @@ def update(self, packagesList, exit_on_package_update_error: bool = True, dry_ru # Add the package to the list of successful packages self.summary['update']['success']['packages'][pkg['name']] = { - 'version': pkg['available_version'], + 'version': pkg['target_version'], 'log': log_content } diff --git a/src/controllers/Package/Package.py b/src/controllers/Package/Package.py index c0dd519..5a0579f 100644 --- a/src/controllers/Package/Package.py +++ b/src/controllers/Package/Package.py @@ -36,7 +36,7 @@ def __init__(self): # Check for package exclusions # #----------------------------------------------------------------------------------------------- - def exclude(self, ignore_exclude): + def exclude(self, ignore_exclusions): try: # Create a new empty list of packages to update packagesToUpdateList = [] @@ -50,8 +50,8 @@ def exclude(self, ignore_exclude): for package in self.packagesToUpdateList: excluded = False - # Check for exclusions and exclude packages only if the ignore_exclude parameter is False - if not ignore_exclude: + # Check for exclusions and exclude packages only if the ignore_exclusions parameter is False + if not ignore_exclusions: # If the package is in the list of packages to exclude (on major update), check if the available version is a major update if excludeOnMajorUpdate: # There can be regex in the excludeOnMajorUpdate list (e.g. apache.*), so we need to convert it to a regex pattern @@ -62,7 +62,7 @@ def exclude(self, ignore_exclude): if re.match(regex, package['name']): # Retrieve the first digit of the current and available versions # If the first digit is different then it is a major update, exclude the package - if package['current_version'].split('.')[0] != package['available_version'].split('.')[0]: + if package['current_version'].split('.')[0] != package['target_version'].split('.')[0]: self.myPackageManagerController.exclude(package['name']) excluded = True @@ -81,7 +81,7 @@ def exclude(self, ignore_exclude): packagesToUpdateList.append({ 'name': package['name'], 'current_version': package['current_version'], - 'available_version': package['available_version'], + 'target_version': package['target_version'], 'excluded': excluded }) @@ -140,7 +140,7 @@ def get_available_packages(self, dist_upgrade: bool = False): # This can be a list of specific packages or all packages # #----------------------------------------------------------------------------------------------- - def update(self, packages_list: list = [], assume_yes: bool = False, ignore_exclude: bool = False, check_updates: bool = False, dist_upgrade: bool = False, keep_oldconf: bool = True, dry_run: bool = False): + def update(self, packages_list: list = [], assume_yes: bool = False, ignore_exclusions: bool = False, check_updates: bool = False, dist_upgrade: bool = False, keep_oldconf: bool = True, dry_run: bool = False): restart_file = '/tmp/linupdate.restart-needed' # Package update summary @@ -175,27 +175,28 @@ def update(self, packages_list: list = [], assume_yes: bool = False, ignore_excl if len(packages_list) > 0: packages_list_temp = [] - # For each package in the list, if no current version or available version is provided, retrieve it + # For each package in the list, if no current version or target version is provided, retrieve it # This is the case when the user uses the --update parameter for package in packages_list: - if 'current_version' not in package or 'available_version' not in package: + if 'current_version' not in package: package['current_version'] = self.myPackageManagerController.get_current_version(package['name']) - package['available_version'] = self.myPackageManagerController.get_available_version(package['name']) - - # If current version or available version have not been found, skip the package - if package['current_version'] == '' or package['available_version'] == '': - continue - - # If current version and available version are the same, skip the package - if package['current_version'] == package['available_version']: - continue - - # Add the package to the list - packages_list_temp.append({ - 'name': package['name'], - 'current_version': package['current_version'], - 'available_version': package['available_version'] - }) + if 'target_version' not in package: + package['target_version'] = self.myPackageManagerController.get_available_version(package['name']) + + # If current version or target version have not been found, skip the package + if package['current_version'] == '' or package['target_version'] == '': + continue + + # If current version and target version are the same, skip the package + if package['current_version'] == package['target_version']: + continue + + # Add the package to the list + packages_list_temp.append({ + 'name': package['name'], + 'current_version': package['current_version'], + 'target_version': package['target_version'] + }) self.packagesToUpdateList = packages_list_temp @@ -206,7 +207,7 @@ def update(self, packages_list: list = [], assume_yes: bool = False, ignore_excl self.packagesToUpdateList = self.get_available_packages(dist_upgrade) # Check for package exclusions - self.exclude(ignore_exclude) + self.exclude(ignore_exclusions) # Count packages to update and packages excluded self.packagesToUpdateCount = 0 @@ -233,11 +234,11 @@ def update(self, packages_list: list = [], assume_yes: bool = False, ignore_excl else: installOrExclude = Fore.GREEN + '✔' + Style.RESET_ALL - table.append(['', package['name'], package['current_version'], package['available_version'], installOrExclude]) + table.append(['', package['name'], package['current_version'], package['target_version'], installOrExclude]) # Print the table list of packages to update # Check prettytable for table with width control https://pypi.org/project/prettytable/ - print(tabulate(table, headers=["", "Package", "Current version", "Available version", "Update decision"], tablefmt="simple"), end='\n\n') + print(tabulate(table, headers=["", "Package", "Current version", "Target version", "Install decision"], tablefmt="simple"), end='\n\n') # If there are no packages to update if self.packagesToUpdateCount == 0: diff --git a/version b/version index e5b8203..084e244 100644 --- a/version +++ b/version @@ -1 +1 @@ -3.5.0 \ No newline at end of file +3.6.0 \ No newline at end of file