def _action(self, info): action = info["action"] filename = info["filename"] fullpath = path.join(self.installpath, filename) # verify file integrity and attempt to repair if corrupted if action == "verify": log.info("Action: verify {0}".format(filename)) hash = info["sha1-before"] if not sha1(fullpath) == hash: if "full-uri" in info: fullformat = None if "full-format" in info: fullformat = info["full-format"] self._backup(filename) log.info("Extract replacement file from {0}".format(info["full-uri"])) with URI(info["full-uri"], package=self.package, format=fullformat, target=fullpath): if not sha1(fullpath) == hash: raise Exception("Integrity check and repair failed") else: raise Exception("Integrity check failed with no repair") # create a new file and verify integrity elif action == "create": log.info("Action: create {0}".format(filename)) hash = info["sha1-after"] if "full-uri" in info: fullformat = None if "full-format" in info: fullformat = info["full-format"] log.info("Extract new file from {0}".format(info["full-uri"])) with URI(info["full-uri"], package=self.package, format=fullformat, target=fullpath): if not sha1(fullpath) == hash: raise Exception("Created file failed integrity check") else: raise Exception("Create action has no URI") # replace file and verify replacement integrity elif action == "replace": log.info("Action: replace {0}".format(filename)) hash = info["sha1-after"] if "full-uri" in info: fullformat = None if "full-format" in info: fullformat = info["full-format"] self._backup(filename) log.info("Extract replacement file from {0}".format(info["full-uri"])) with URI(info["full-uri"], package=self.package, format=fullformat, target=fullpath): if not sha1(fullpath) == hash: raise Exception("Replacement file failed integrity check") else: raise Exception("Replacement action has no URI") # delete a file with little recourse elif action == "delete": log.info("Action: delete {0}".format(filename)) self._backup(filename) # intentional exception to test rollbacks elif action == "exception": log.warning("Action: exception") raise Exception("Intentional exception raised") # /shrug else: raise Exception("Unsupported action %s" % (action))
def apply(self, installpath, backuppath=None): """ Apply the manifest against the given installation path. """ self.installpath = installpath manifest = self.manifest # fetch and verify package if "package-uri" in manifest: packageuri = URI(manifest["package-uri"]) packagename = path.basename(packageuri["path"]) packagepath = path.join(tempfile.gettempdir(), packagename) packageformat = None if "package-format" in manifest: packageformat = manifest["package-format"] # download package file log.info("Fetching update package from {0}".format(packageuri.uri)) packageuri.fetch(packagepath) packageuri.close() # integrity check if "package-sha1" in manifest: packagesha1 = sha1(packagepath) if not packagesha1 == manifest["package-sha1"]: raise Exception("Package integrity check failed") self.package = Package(packagepath, packageformat) # create spot for backup files if backuppath is None: self.backuppath = tempfile.mkdtemp(prefix="update_") else: self.backuppath = backuppath if not path.isdir(self.backuppath): log.debug("Backup path {0} does not exist; creating".format(self.backuppath)) os.mkdir(self.backuppath) # start applying actions try: for info in manifest.actions: self._action(info) # handle update errors by rolling back except Exception as err: log.exception("Exception eaten; beginning rollback") for root, dirs, files in os.walk(self.backuppath): relpath = path.relpath(root, self.backuppath) if relpath == ".": relpath = "" for filename in files: filepath = path.join(relpath, filename) log.info("Rolling back file %s" % (filepath)) self._restore(filepath) log.info("Rollback completed; raising exception") raise # clean up finally: self.cleanup = False shutil.rmtree(self.backuppath, onerror=self._onerror) self.package.close() os.remove(packagepath) if self.cleanup: return self.backuppath
def fetch(self, target=None): parse = self.parse # local file if parse.scheme == "file": self.handle = File(parse.path, "r", format=self.format) if target: self.handle.decompress(target) self.handle.close() self.handle = File(target, "r") # package file elif parse.scheme == "package": if self.package is None: raise Exception("No package specified") filename = parse.path.lstrip("/") self.handle = self.package.open(filename, "r", format=self.format) if target: self.handle.decompress(target) self.handle.close() self.handle = File(target, "r") # remote http resource elif parse.scheme in ("http", "https"): failure = True tries = 0 while failure: failure = False try: if target: target = path.normpath(target) log.info("Downloading {0} to file {1}".format(self.uri, target)) downloadpath, headers = urllib.urlretrieve(self.uri) self.handle = File(downloadpath, "r", format=self.format) self.handle.decompress(target) self.handle.close() self.handle = File(target, "r") else: self.handle = urllib2.urlopen(self.uri) except: failure = True if tries < 3: log.info("Problem retrieving URI, retry in 3 seconds...") log.debug(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) time.sleep(3) else: log.info("Failed retrieving URI") raise finally: tries += 1 return self.handle else: raise Exception("Unsupported URI scheme {0}".format(parse.scheme)) return self.handle