OSPatching/patch/UbuntuPatching.py (75 lines of code) (raw):

#!/usr/bin/python # # Copyright 2014 Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import logging from Utils.WAAgentUtil import waagent from AbstractPatching import AbstractPatching class UbuntuPatching(AbstractPatching): def __init__(self, hutil): super(UbuntuPatching,self).__init__(hutil) self.update_cmd = 'apt-get update' self.check_cmd = 'apt-get -qq -s upgrade' self.check_cmd_distupgrade = 'apt-get -qq -s dist-upgrade' self.check_security_suffix = ' -o Dir::Etc::SourceList=/etc/apt/security.sources.list' waagent.Run('grep "-security" /etc/apt/sources.list | sudo grep -v "#" > /etc/apt/security.sources.list') self.download_cmd = 'apt-get -d -y install' self.patch_cmd = 'apt-get -y -q --force-yes -o Dpkg::Options::="--force-confdef" install' self.fix_cmd = 'dpkg --configure -a --force-confdef' self.status_cmd = 'apt-cache show' self.pkg_query_cmd = 'dpkg-query -L' # Avoid a config prompt os.environ['DEBIAN_FRONTEND']='noninteractive' def install(self): """ Install for dependencies. """ # Update source.list waagent.Run(self.update_cmd, False) # /var/run/reboot-required is not created unless the update-notifier-common package is installed retcode = waagent.Run('apt-get -y install update-notifier-common') if retcode > 0: self.hutil.error("Failed to install update-notifier-common") def try_package_with_autofix(self, cmd): retcode, output = waagent.RunGetOutput(cmd) if retcode == 0: return retcode, output # An error occurred while running the command. Try to recover. # Unfortunately apt-get returns code 100 regardless of the error encountered, # so we can't smartly detect the cause of failure self.log_and_syslog(logging.WARNING, "Error running command ({0}). Will try to correct package state ({1}). Error was {2}".format(cmd, self.fix_cmd, output)) retcode, output = waagent.RunGetOutput(self.fix_cmd) if retcode != 0: self.log_and_syslog(logging.WARNING, "Error correcting package state ({0}). Error was {1}".format(self.fix_cmd, output)) retcode, output = waagent.RunGetOutput(cmd) if retcode != 0: self.log_and_syslog(logging.WARNING, "Unable to run ({0}) on second attempt. Giving up. Error was {1}".format(cmd, output)) return retcode, output def check(self, category): """ Check valid upgrades, Return the package list to download & upgrade """ # Perform upgrade or dist-upgrade as appropriate if self.dist_upgrade_all: self.log_and_syslog(logging.INFO, "Performing dist-upgrade for ALL packages") check_cmd = self.check_cmd_distupgrade else: check_cmd = self.check_cmd # If upgrading only required/security patches, append the command suffix # Otherwise, assume all packages will be upgraded if category == self.category_required: check_cmd = check_cmd + self.check_security_suffix retcode, output = self.try_package_with_autofix(check_cmd) to_download = [line.split()[1] for line in output.split('\n') if line.startswith('Inst')] # Azure repo assumes upgrade may have dependency changes if retcode != 0: self.log_and_syslog(logging.WARNING, "Failed to get list of upgradeable packages") elif self.is_string_none_or_empty(self.dist_upgrade_list): self.log_and_syslog(logging.INFO, "Dist upgrade list not specified, will perform normal patch") elif not os.path.isfile(self.dist_upgrade_list): self.log_and_syslog(logging.WARNING, "Dist upgrade list was specified but file [{0}] does not exist".format(self.dist_upgrade_list)) else: self.log_and_syslog(logging.INFO, "Running dist-upgrade using {0}".format(self.dist_upgrade_list)) self.check_azure_cmd = 'apt-get -qq -s dist-upgrade -o Dir::Etc::SourceList={0}'.format(self.dist_upgrade_list) retcode, azoutput = self.try_package_with_autofix(self.check_azure_cmd) azure_to_download = [line.split()[1] for line in azoutput.split('\n') if line.startswith('Inst')] to_download += list(set(azure_to_download) - set(to_download)) return retcode, to_download def download_package(self, package): return waagent.Run(self.download_cmd + ' ' + package) def patch_package(self, package): retcode, output = self.try_package_with_autofix(self.patch_cmd + ' ' + package) return retcode def check_reboot(self): self.reboot_required = os.path.isfile('/var/run/reboot-required') def get_pkg_needs_restart(self): fd = '/var/run/reboot-required.pkgs' if not os.path.isfile(fd): return [] return waagent.GetFileContents(fd).split('\n') def report(self): """ TODO: Report the detail status of patching """ for package_patched in self.patched: retcode,output = waagent.RunGetOutput(self.status_cmd + ' ' + package_patched) output = output.split('\n\n')[0] self.hutil.log(output)