def __init__(self, user, gitReadHash="master", ignoreVersionLocks=False, initialized=False): self.__subusers = None self.__changeLog = u"" self.commit_message = None self.__changed = False self.logOutputVerbosity = 2 self.initialized = initialized self.lastVerbosityLevel = None self.ignoreVersionLocks = ignoreVersionLocks if "SUBUSER_VERBOSITY" in os.environ: try: self.logOutputVerbosity = int(os.environ["SUBUSER_VERBOSITY"]) except ValueError: subuserlib.print.printWithoutCrashing( "Invalid verbosity setting! Verbosity may be set to any integer." ) self.__repositories = None self.gitRepository = None self.gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self, user) self.registryDir = self.user.config["registry-dir"] self.logFilePath = os.path.join(self.registryDir, "commit_log") self.gitRepository = GitRepository(self.user, self.registryDir)
def getSubuserVersion(): if subuserlib.test.testing: return "0.5" with open(subuserlib.paths.getSubuserDataFile("VERSION")) as f: stableVersion = f.read().strip() if os.path.exists(os.path.join(subuserlib.paths.getSubuserDir(), ".git")): gitRepo = GitRepository(subuserlib.paths.getSubuserDir()) gitHash = gitRepo.getHashOfRef("HEAD") return stableVersion + "-dev-" + gitHash else: return stableVersion
def getSubuserVersion(): if subuserlib.test.testing: return "0.5" with open(subuserlib.paths.getSubuserDataFile("VERSION")) as f: stableVersion = f.read().strip() if os.path.exists(os.path.join(subuserlib.paths.getSubuserDir(),".git")): gitRepo = GitRepository(subuserlib.paths.getSubuserDir()) gitHash = gitRepo.getHashOfRef("HEAD") return stableVersion+"-dev-"+gitHash else: return stableVersion
def getSubuserVersion(user): if subuserlib.test.testing: return "0.5" with open(subuserlib.paths.getSubuserDataFile("VERSION")) as f: stableVersion = f.read().strip() if os.path.exists(os.path.join(subuserlib.paths.getSubuserDir(), ".git")): gitRepo = GitRepository(user, subuserlib.paths.getSubuserDir()) gitHash = gitRepo.getHashOfRef("HEAD") devVersionString = stableVersion + "-dev-" + gitHash if gitRepo.doesHaveUncommittedChanges("HEAD"): devVersionString += "-dirty" return devVersionString else: return stableVersion
def getSubuserVersion(user): if subuserlib.test.testing: return "0.5" with open(subuserlib.paths.getSubuserDataFile("VERSION")) as f: stableVersion = f.read().strip() if os.path.exists(os.path.join(subuserlib.paths.getSubuserDir(),".git")): gitRepo = GitRepository(user,subuserlib.paths.getSubuserDir()) gitHash = gitRepo.getHashOfRef("HEAD") devVersionString = stableVersion+"-dev-"+gitHash if gitRepo.doesHaveUncommittedChanges("HEAD"): devVersionString += "-dirty" return devVersionString else: return stableVersion
def __init__(self, user, gitReadHash="master", ignoreVersionLocks=False, initialized=False): self.__subusers = None self.__changeLog = u"" self.__changed = False self.__logOutputVerbosity = 1 self.initialized = initialized self.ignoreVersionLocks = ignoreVersionLocks if "SUBUSER_VERBOSITY" in os.environ: try: self.__logOutputVerbosity = int( os.environ["SUBUSER_VERBOSITY"]) except ValueError: subuserlib.print.printWithoutCrashing( "Invalid verbosity setting! Verbosity may be set to any integer." ) self.__repositories = None self.__gitRepository = None self.__gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self, user) self.__gitRepository = GitRepository( self.getUser(), self.getUser().getConfig()["registry-dir"])
def __init__(self,user,gitReadHash="master"): self.__subusers = None self.__changeLog = "" self.__changed = False self.__logOutputVerbosity = 2 self.__repositories = None self.__gitRepository = None self.__gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self,user) self.__gitRepository = GitRepository(self.getUser().getConfig()["registry-dir"]) self._ensureGitRepoInitialized()
def __init__(self, user, name, gitOriginURI=None, gitCommitHash=None, temporary=False, sourceDir=None): """ Repositories can either be managed by git, or simply be normal directories on the user's computer. If ``sourceDir`` is not set to None, then ``gitOriginURI`` is ignored and the repository is assumed to be a simple directory. """ self.name = name self.gitOriginURI = gitOriginURI self.gitCommitHash = gitCommitHash self.temporary = temporary self.sourceDir = sourceDir self.__fileStructure = None UserOwnedObject.__init__(self, user) super().__init__() self.gitRepository = GitRepository(user, self.repoPath) if not self.isPresent(): self.updateSources(initialUpdate=True) if self.isPresent(): self.repoConfig = self.loadRepoConfig() self.loadImageSources()
def __init__(self,user,name,gitOriginURI=None,gitCommitHash=None,temporary=False,sourceDir=None): """ Repositories can either be managed by git, or simply be normal directories on the user's computer. If ``sourceDir`` is not set to None, then ``gitOriginURI`` is ignored and the repository is assumed to be a simple directory. """ self.name = name self.gitOriginURI = gitOriginURI self.gitCommitHash = gitCommitHash self.temporary = temporary self.sourceDir = sourceDir self.__fileStructure = None UserOwnedObject.__init__(self,user) super().__init__() self.gitRepository = GitRepository(user,self.repoPath) if not self.isPresent(): self.updateSources(initialUpdate=True) if self.isPresent(): self.repoConfig = self.loadRepoConfig() self.loadImageSources()
def __init__(self, user, name, gitOriginURI=None, gitCommitHash=None, temporary=False, sourceDir=None): """ Repositories can either be managed by git, or simply be normal directories on the user's computer. If ``sourceDir`` is not set to None, then ``gitOriginURI`` is ignored and the repository is assumed to be a simple directory. """ self.__name = name self.__gitOriginURI = gitOriginURI self.__lastGitCommitHash = gitCommitHash self.__temporary = temporary self.__sourceDir = sourceDir UserOwnedObject.__init__(self, user) self.__gitRepository = GitRepository(self.getRepoPath()) self.loadProgramSources()
def __init__(self,user,gitReadHash="master", ignoreVersionLocks=False, initialized = False): self.__subusers = None self.__changeLog = u"" self.commit_message = None self.__changed = False self.logOutputVerbosity = 2 self.initialized = initialized self.lastVerbosityLevel = None self.ignoreVersionLocks = ignoreVersionLocks if "SUBUSER_VERBOSITY" in os.environ: try: self.logOutputVerbosity = int(os.environ["SUBUSER_VERBOSITY"]) except ValueError: subuserlib.print.printWithoutCrashing("Invalid verbosity setting! Verbosity may be set to any integer.") self.__repositories = None self.gitRepository = None self.gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self,user) self.registryDir = self.user.config["registry-dir"] self.logFilePath = os.path.join(self.registryDir,"commit_log") self.gitRepository = GitRepository(self.user,self.registryDir)
class Registry(userOwnedObject.UserOwnedObject): def __init__(self,user,gitReadHash="master", ignoreVersionLocks=False, initialized = False): self.__subusers = None self.__changeLog = u"" self.commit_message = None self.__changed = False self.logOutputVerbosity = 2 self.initialized = initialized self.lastVerbosityLevel = None self.ignoreVersionLocks = ignoreVersionLocks if "SUBUSER_VERBOSITY" in os.environ: try: self.logOutputVerbosity = int(os.environ["SUBUSER_VERBOSITY"]) except ValueError: subuserlib.print.printWithoutCrashing("Invalid verbosity setting! Verbosity may be set to any integer.") self.__repositories = None self.gitRepository = None self.gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self,user) self.registryDir = self.user.config["registry-dir"] self.logFilePath = os.path.join(self.registryDir,"commit_log") self.gitRepository = GitRepository(self.user,self.registryDir) @property def subusers(self): if self.__subusers is None: self.__subusers = subusers.Subusers(self.user) return self.__subusers @property def repositories(self): if not self.__repositories: self.__repositories = repositories.Repositories(self.user) return self.__repositories def ensureGitRepoInitialized(self): if not os.path.exists(os.path.join(self.user.config["registry-dir"],".git")): self.initialized = False # Ensure git is setup before we start to make changes. self.gitRepository.assertGitSetup() self.user.endUser.makedirs(self.user.config["registry-dir"]) try: self.gitRepository.run(["init"]) except subuserlib.classes.gitRepository.GitException as e: # Remove partially initialized repository when 'init' command failed shutil.rmtree(self.gitRepository.path) sys.exit("Git failed with: '%s'." % str(e).strip()) self.logChange("Initial commit.") self.commit("Initial commit.",_no_lock_needed = True) self.initialized = True def log(self,message,verbosityLevel=1,notify=False): """ If the current verbosity level is equal to or greater than verbosityLevel, print the message to the screen. If the current verbosity level is equal to or greater than verbosityLevel minus one, add the message to the log. Do not mark the registry as changed. The notify option will create a popup dialog with the message if the notify-send command exists. """ message = message.rstrip() if (verbosityLevel-1) <= self.logOutputVerbosity: self.__changeLog = self.__changeLog + message + u"\n" if self.lastVerbosityLevel == 2 and self.logOutputVerbosity == 2 and sys.stdout.isatty(): CURSOR_UP_ONE = '\x1b[1A' ERASE_LINE = '\x1b[2K' print(CURSOR_UP_ONE + ERASE_LINE + CURSOR_UP_ONE) if verbosityLevel <= self.logOutputVerbosity: subuserlib.print.printWithoutCrashing(message) if notify and subuserlib.executablePath.which("notify-send"): self.user.endUser.call(["notify-send",message]) self.lastVerbosityLevel = verbosityLevel def logChange(self,message,verbosityLevel=1): """ Add a log message to the registry's change log, and mark the registry as changed. """ self.log(message,verbosityLevel=verbosityLevel) self.__changed = True def setChanged(self,changed=True): self.__changed = changed def logRenameCommit(self, message): """ Add a new message to the top of the log. """ self.__changeLog = message + u"\n" + self.__changeLog def commit(self,message=None,_no_lock_needed=False): """ Git commit the changes to the registry files, installed-miages.json and subusers.json. """ if (not self.user._has_lock) and (not _no_lock_needed): sys.exit("Programmer error. Committing to registry without first aquiring lock! Please report this incident to: https://github.com/subuser-security/subuser/issues") if self.__changed: self.repositories.save() self.subusers.save() with self.user.endUser.get_file(self.logFilePath) as fd: fd.write(self.__changeLog) self.gitRepository.run(["add","."]) if message is None: if self.commit_message is not None: message = self.commit_message else: message = self.__changeLog self.gitRepository.commit(message) # Log to live log announcement = {} announcement["commit"] = self.gitRepository.getHashOfRef("master") self.logToLiveLog(announcement) self.__changed = False self.__changeLog = u"" def logToLiveLog(self,announcement): announcementJson = json.dumps(announcement) liveLogDir=os.path.join(self.user.endUser.homeDir,".subuser/registry-live-log") if os.path.isdir(liveLogDir): for liveLogPid in os.listdir(liveLogDir): liveLogPath = os.path.join(liveLogDir,liveLogPid) try: liveLog = os.open(liveLogPath,os.O_WRONLY|os.O_NONBLOCK) os.write(liveLog,announcementJson) except OSError: pass # TODO Note: We don't close the file descriptors, because doing so makes the pipe close on the other end too. This would be a file descriptor leak if this method was used in any long running process(which it is not). def cleanOutOldPermissions(self): for permissions_folder_name in self.gitRepository.getFileStructureAtCommit(self.gitReadHash).lsFolders("permissions"): exists = os.path.exists(os.path.join(self.registryDir,"permissions",permissions_folder_name)) if exists and permissions_folder_name not in self.subusers: self.logChange("Removing left over permissions for no-longer extant subuser %s"%permissions_folder_name,2) try: self.gitRepository.run(["rm","-r",os.path.join("permissions",permissions_folder_name)]) except subuserlib.classes.gitRepository.GitException as e: self.log(" %s"%str(e))
class Registry(userOwnedObject.UserOwnedObject): def __init__(self, user, gitReadHash="master", ignoreVersionLocks=False, initialized=False): self.__subusers = None self.__changeLog = u"" self.commit_message = None self.__changed = False self.logOutputVerbosity = 2 self.initialized = initialized self.lastVerbosityLevel = None self.ignoreVersionLocks = ignoreVersionLocks if "SUBUSER_VERBOSITY" in os.environ: try: self.logOutputVerbosity = int(os.environ["SUBUSER_VERBOSITY"]) except ValueError: subuserlib.print.printWithoutCrashing( "Invalid verbosity setting! Verbosity may be set to any integer." ) self.__repositories = None self.gitRepository = None self.gitReadHash = gitReadHash userOwnedObject.UserOwnedObject.__init__(self, user) self.registryDir = self.user.config["registry-dir"] self.logFilePath = os.path.join(self.registryDir, "commit_log") self.gitRepository = GitRepository(self.user, self.registryDir) @property def subusers(self): if self.__subusers is None: self.__subusers = subusers.Subusers(self.user) return self.__subusers @property def repositories(self): if not self.__repositories: self.__repositories = repositories.Repositories(self.user) return self.__repositories def ensureGitRepoInitialized(self): if not os.path.exists( os.path.join(self.user.config["registry-dir"], ".git")): self.initialized = False # Ensure git is setup before we start to make changes. self.gitRepository.assertGitSetup() self.user.endUser.makedirs(self.user.config["registry-dir"]) try: self.gitRepository.run(["init"]) except subuserlib.classes.gitRepository.GitException as e: # Remove partially initialized repository when 'init' command failed shutil.rmtree(self.gitRepository.path) sys.exit("Git failed with: '%s'." % str(e).strip()) self.logChange("Initial commit.") self.commit("Initial commit.", _no_lock_needed=True) self.initialized = True def log(self, message, verbosityLevel=1, notify=False): """ If the current verbosity level is equal to or greater than verbosityLevel, print the message to the screen. If the current verbosity level is equal to or greater than verbosityLevel minus one, add the message to the log. Do not mark the registry as changed. The notify option will create a popup dialog with the message if the notify-send command exists. """ message = message.rstrip() if (verbosityLevel - 1) <= self.logOutputVerbosity: self.__changeLog = self.__changeLog + message + u"\n" if self.lastVerbosityLevel == 2 and self.logOutputVerbosity == 2 and sys.stdout.isatty( ): CURSOR_UP_ONE = '\x1b[1A' ERASE_LINE = '\x1b[2K' print(CURSOR_UP_ONE + ERASE_LINE + CURSOR_UP_ONE) if verbosityLevel <= self.logOutputVerbosity: subuserlib.print.printWithoutCrashing(message) if notify and subuserlib.executablePath.which("notify-send"): self.user.endUser.call(["notify-send", message]) self.lastVerbosityLevel = verbosityLevel def logChange(self, message, verbosityLevel=1): """ Add a log message to the registry's change log, and mark the registry as changed. """ self.log(message, verbosityLevel=verbosityLevel) self.__changed = True def setChanged(self, changed=True): self.__changed = changed def logRenameCommit(self, message): """ Add a new message to the top of the log. """ self.__changeLog = message + u"\n" + self.__changeLog def commit(self, message=None, _no_lock_needed=False): """ Git commit the changes to the registry files, installed-miages.json and subusers.json. """ if (not self.user._has_lock) and (not _no_lock_needed): sys.exit( "Programmer error. Committing to registry without first aquiring lock! Please report this incident to: https://github.com/subuser-security/subuser/issues" ) if self.__changed: self.repositories.save() self.subusers.save() with self.user.endUser.get_file(self.logFilePath) as fd: fd.write(self.__changeLog) self.gitRepository.run(["add", "."]) if message is None: if self.commit_message is not None: message = self.commit_message else: message = self.__changeLog self.gitRepository.commit(message) # Log to live log announcement = {} announcement["commit"] = self.gitRepository.getHashOfRef("master") self.logToLiveLog(announcement) self.__changed = False self.__changeLog = u"" def logToLiveLog(self, announcement): announcementJson = json.dumps(announcement) liveLogDir = os.path.join(self.user.endUser.homeDir, ".subuser/registry-live-log") if os.path.isdir(liveLogDir): for liveLogPid in os.listdir(liveLogDir): liveLogPath = os.path.join(liveLogDir, liveLogPid) try: liveLog = os.open(liveLogPath, os.O_WRONLY | os.O_NONBLOCK) os.write(liveLog, announcementJson) except OSError: pass # TODO Note: We don't close the file descriptors, because doing so makes the pipe close on the other end too. This would be a file descriptor leak if this method was used in any long running process(which it is not). def cleanOutOldPermissions(self): for permissions_folder_name in self.gitRepository.getFileStructureAtCommit( self.gitReadHash).lsFolders("permissions"): exists = os.path.exists( os.path.join(self.registryDir, "permissions", permissions_folder_name)) if exists and permissions_folder_name not in self.subusers: self.logChange( "Removing left over permissions for no-longer extant subuser %s" % permissions_folder_name, 2) try: self.gitRepository.run([ "rm", "-r", os.path.join("permissions", permissions_folder_name) ]) except subuserlib.classes.gitRepository.GitException as e: self.log(" %s" % str(e))
class Repository(OrderedDict,UserOwnedObject,Describable): def __init__(self,user,name,gitOriginURI=None,gitCommitHash=None,temporary=False,sourceDir=None): """ Repositories can either be managed by git, or simply be normal directories on the user's computer. If ``sourceDir`` is not set to None, then ``gitOriginURI`` is ignored and the repository is assumed to be a simple directory. """ self.name = name self.gitOriginURI = gitOriginURI self.gitCommitHash = gitCommitHash self.temporary = temporary self.sourceDir = sourceDir self.__fileStructure = None UserOwnedObject.__init__(self,user) super().__init__() self.gitRepository = GitRepository(user,self.repoPath) if not self.isPresent(): self.updateSources(initialUpdate=True) if self.isPresent(): self.repoConfig = self.loadRepoConfig() self.loadImageSources() @property def uri(self): if self.isLocal: return self.sourceDir else: return self.gitOriginURI @property def fileStructure(self): if self.__fileStructure is None: if self.isLocal: self.__fileStructure = BasicFileStructure(self.sourceDir) else: if self.gitCommitHash is None: self.updateGitCommitHash() self.__fileStructure = self.gitRepository.getFileStructureAtCommit(self.gitCommitHash) return self.__fileStructure @property def displayName(self): """ How should we refer to this repository when communicating with the user? """ if self.temporary: if self.isLocal: return self.sourceDir else: return self.gitOriginURI else: return self.name def describe(self): print("Repository: "+self.displayName) print("------------") if self.isLocal: print("Is a local(non-git) repository.") if not self.temporary: print("Located at: " + self.repoPath) if self.temporary: print("Is a temporary repository.") else: if not self.isLocal: print("Cloned from: "+self.gitOriginURI) print("Currently at commit: "+self.gitCommitHash) def getSortedList(self): """ Return a list of image sources sorted by name. """ return list(sorted(self.values(),key=lambda imageSource:imageSource.name)) @property def repoPath(self): """ Get the path of the repo's sources on disk. """ if self.isLocal: return self.sourceDir else: return os.path.join(self.user.config["repositories-dir"],self.name) def loadRepoConfig(self): """ Either returns the config as a dictionary or None if no configuration exists or can be parsed. """ def verifyPaths(dictionary,paths): """ Looks through a dictionary at all entries that can be considered to be paths, and ensures that they do not contain any relative upwards references. Throws a ValueError if they do. """ for path in paths: if path in dictionary and path.startswith("../") or "/../" in path: raise ValueError("Paths in .subuser.json may not be relative to a higher directory.") if self.fileStructure.exists("./.subuser.json"): configFileContents = self.fileStructure.read("./.subuser.json") else: return None repoConfig = json.loads(configFileContents) # Validate untrusted input verifyPaths(repoConfig,["image-sources-dir"]) if "explicit-image-sources" in repoConfig: for explicitImageSource in repoConfig["explicit-image-sources"]: verifyPaths(explicitImageSource,["image-file","permissions-file","build-context"]) return repoConfig @property def imageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ return os.path.join(self.repoPath,self.relativeImageSourcesDir) @property def relativeImageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ repoConfig = self.repoConfig if repoConfig and "image-sources-dir" in repoConfig: return repoConfig["image-sources-dir"] else: return "./" def isInUse(self): """ Are there any installed images or subusers from this repository? """ for _,installedImage in self.user.installedImages.items(): if self.name == installedImage.sourceRepoId: return True for _,subuser in self.user.registry.subusers.items(): try: if self.name == subuser.imageSource.repo.name: return True except subuserlib.classes.subuser.NoImageSourceException: pass return False @property def isLocal(self): if self.sourceDir: return True return False def removeGitRepo(self): """ Remove the downloaded git repo associated with this repository from disk. """ if not self.isLocal: shutil.rmtree(self.repoPath) def isPresent(self): """ Returns True if the repository's files are present on the system. (Cloned or local) """ return os.path.exists(self.repoPath) def updateSources(self,initialUpdate=False): """ Pull(or clone) the repo's ImageSources from git origin. """ if self.isLocal: return if not self.isPresent(): new = True self.user.registry.log("Cloning repository "+self.name+" from "+self.gitOriginURI) if self.gitRepository.clone(self.gitOriginURI) != 0: self.user.registry.log("Clone failed.") return else: new = False try: self.gitRepository.run(["fetch","--all"]) except Exception: # For some reason, git outputs normal messages to stderr. pass if self.updateGitCommitHash(): if not new: self.user.registry.logChange("Updated repository "+self.displayName) if not initialUpdate: self.loadImageSources() def serializeToDict(self): """ Return a dictionary which describes the image sources available in this repository. """ imageSourcesDict = {} for name,imageSource in self.items(): imageSourcesDict[name] = imageSource.serializeToDict() return imageSourcesDict def loadImageSources(self): """ Load ImageSources from disk into memory. """ imageNames = self.fileStructure.lsFolders(self.relativeImageSourcesDir) for imageName in imageNames: imageSource = ImageSource(self.user,self,imageName) if self.fileStructure.exists(imageSource.getRelativePermissionsFilePath()): self[imageName] = imageSource if self.repoConfig is not None and "explicit-image-sources" in self.repoConfig: for imageName,config in self.repoConfig["explicit-image-sources"].items(): assert config is not None self[imageName] = ImageSource(self.user,self,imageName,explicitConfig = config) def updateGitCommitHash(self): """ Update the internally stored git commit hash to the current git HEAD of the repository. Returns True if the repository has been updated. Otherwise false. """ if self.isLocal: return True # Default master = "refs/remotes/origin/master" newCommitHash = self.gitRepository.getHashOfRef(master) # First we check for version constraints on the repository. if self.gitRepository.getFileStructureAtCommit(master).exists("./.subuser.json"): configFileContents = self.gitRepository.getFileStructureAtCommit(master).read("./.subuser.json") configAtMaster = json.loads(configFileContents) if "subuser-version-constraints" in configAtMaster: versionConstraints = configAtMaster["subuser-version-constraints"] subuserVersion = subuserlib.version.getSubuserVersion(self.user) for constraint in versionConstraints: if not len(constraint) == 3: raise SyntaxError("Error in .subuser.json file. Invalid subuser-version-constraints."+ str(versionConstraints)) op,version,commit = constraint from operator import lt,le,eq,ge,gt operators = {"<":lt,"<=":le,"==":eq,">=":ge,">":gt} try: matched = operators[op](subuserVersion,version) except KeyError: raise SyntaxError("Error in .subuser.json file. Invalid subuser-version-constraints. \""+op+"\" is not a valid operator.\n\n"+ str(versionConstraints)) if matched: try: newCommitHash = self.gitRepository.getHashOfRef("refs/remotes/origin/"+commit) except OSError as e: if len(commit) == 40: newCommitHash = commit else: raise e break else: raise SyntaxError("Error reading .subuser.json file, no version constraints matched the current subuser version ("+subuserVersion+").\n\n"+str(versionConstraints)) updated = not (newCommitHash == self.gitCommitHash) self.gitCommitHash = newCommitHash self.__fileStructure = None return updated
class Repository(OrderedDict, UserOwnedObject, Describable): def __init__(self, user, name, gitOriginURI=None, gitCommitHash=None, temporary=False, sourceDir=None): """ Repositories can either be managed by git, or simply be normal directories on the user's computer. If ``sourceDir`` is not set to None, then ``gitOriginURI`` is ignored and the repository is assumed to be a simple directory. """ self.name = name self.gitOriginURI = gitOriginURI self.gitCommitHash = gitCommitHash self.temporary = temporary self.sourceDir = sourceDir self.__fileStructure = None UserOwnedObject.__init__(self, user) super().__init__() self.gitRepository = GitRepository(user, self.repoPath) if not self.isPresent(): self.updateSources(initialUpdate=True) if self.isPresent(): self.repoConfig = self.loadRepoConfig() self.loadImageSources() @property def uri(self): if self.isLocal: return self.sourceDir else: return self.gitOriginURI @property def fileStructure(self): if self.__fileStructure is None: if self.isLocal: self.__fileStructure = BasicFileStructure(self.sourceDir) else: if self.gitCommitHash is None: self.updateGitCommitHash() self.__fileStructure = self.gitRepository.getFileStructureAtCommit( self.gitCommitHash) return self.__fileStructure @property def displayName(self): """ How should we refer to this repository when communicating with the user? """ if self.temporary: if self.isLocal: return self.sourceDir else: return self.gitOriginURI else: return self.name def describe(self): print("Repository: " + self.displayName) print("------------") if self.isLocal: print("Is a local(non-git) repository.") if not self.temporary: print("Located at: " + self.repoPath) if self.temporary: print("Is a temporary repository.") else: if not self.isLocal: print("Cloned from: " + self.gitOriginURI) print("Currently at commit: " + self.gitCommitHash) def getSortedList(self): """ Return a list of image sources sorted by name. """ return list( sorted(self.values(), key=lambda imageSource: imageSource.name)) @property def repoPath(self): """ Get the path of the repo's sources on disk. """ if self.isLocal: return self.sourceDir else: return os.path.join(self.user.config["repositories-dir"], self.name) def loadRepoConfig(self): """ Either returns the config as a dictionary or None if no configuration exists or can be parsed. """ def verifyPaths(dictionary, paths): """ Looks through a dictionary at all entries that can be considered to be paths, and ensures that they do not contain any relative upwards references. Throws a ValueError if they do. """ for path in paths: if path in dictionary and path.startswith( "../") or "/../" in path: raise ValueError( "Paths in .subuser.json may not be relative to a higher directory." ) if self.fileStructure.exists("./.subuser.json"): configFileContents = self.fileStructure.read("./.subuser.json") else: return None repoConfig = json.loads(configFileContents) # Validate untrusted input verifyPaths(repoConfig, ["image-sources-dir"]) if "explicit-image-sources" in repoConfig: for explicitImageSource in repoConfig["explicit-image-sources"]: verifyPaths( explicitImageSource, ["image-file", "permissions-file", "build-context"]) return repoConfig @property def imageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ return os.path.join(self.repoPath, self.relativeImageSourcesDir) @property def relativeImageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ repoConfig = self.repoConfig if repoConfig and "image-sources-dir" in repoConfig: return repoConfig["image-sources-dir"] else: return "./" def isInUse(self): """ Are there any installed images or subusers from this repository? """ for _, installedImage in self.user.installedImages.items(): if self.name == installedImage.sourceRepoId: return True for _, subuser in self.user.registry.subusers.items(): try: if self.name == subuser.imageSource.repo.name: return True except subuserlib.classes.subuser.NoImageSourceException: pass return False @property def isLocal(self): if self.sourceDir: return True return False def removeGitRepo(self): """ Remove the downloaded git repo associated with this repository from disk. """ if not self.isLocal: shutil.rmtree(self.repoPath) def isPresent(self): """ Returns True if the repository's files are present on the system. (Cloned or local) """ return os.path.exists(self.repoPath) def updateSources(self, initialUpdate=False): """ Pull(or clone) the repo's ImageSources from git origin. """ if self.isLocal: return if not self.isPresent(): new = True self.user.registry.log("Cloning repository " + self.name + " from " + self.gitOriginURI) if self.gitRepository.clone(self.gitOriginURI) != 0: self.user.registry.log("Clone failed.") return else: new = False try: self.gitRepository.run(["fetch", "--all"]) except Exception: # For some reason, git outputs normal messages to stderr. pass if self.updateGitCommitHash(): if not new: self.user.registry.logChange("Updated repository " + self.displayName) if not initialUpdate: self.loadImageSources() def serializeToDict(self): """ Return a dictionary which describes the image sources available in this repository. """ imageSourcesDict = {} for name, imageSource in self.items(): imageSourcesDict[name] = imageSource.serializeToDict() return imageSourcesDict def loadImageSources(self): """ Load ImageSources from disk into memory. """ imageNames = self.fileStructure.lsFolders(self.relativeImageSourcesDir) for imageName in imageNames: imageSource = ImageSource(self.user, self, imageName) if self.fileStructure.exists( imageSource.getRelativePermissionsFilePath()): self[imageName] = imageSource if self.repoConfig is not None and "explicit-image-sources" in self.repoConfig: for imageName, config in self.repoConfig[ "explicit-image-sources"].items(): assert config is not None self[imageName] = ImageSource(self.user, self, imageName, explicitConfig=config) def updateGitCommitHash(self): """ Update the internally stored git commit hash to the current git HEAD of the repository. Returns True if the repository has been updated. Otherwise false. """ if self.isLocal: return True # Default master = "refs/remotes/origin/master" newCommitHash = self.gitRepository.getHashOfRef(master) # First we check for version constraints on the repository. if self.gitRepository.getFileStructureAtCommit(master).exists( "./.subuser.json"): configFileContents = self.gitRepository.getFileStructureAtCommit( master).read("./.subuser.json") configAtMaster = json.loads(configFileContents) if "subuser-version-constraints" in configAtMaster: versionConstraints = configAtMaster[ "subuser-version-constraints"] subuserVersion = subuserlib.version.getSubuserVersion( self.user) for constraint in versionConstraints: if not len(constraint) == 3: raise SyntaxError( "Error in .subuser.json file. Invalid subuser-version-constraints." + str(versionConstraints)) op, version, commit = constraint from operator import lt, le, eq, ge, gt operators = { "<": lt, "<=": le, "==": eq, ">=": ge, ">": gt } try: matched = operators[op](subuserVersion, version) except KeyError: raise SyntaxError( "Error in .subuser.json file. Invalid subuser-version-constraints. \"" + op + "\" is not a valid operator.\n\n" + str(versionConstraints)) if matched: try: newCommitHash = self.gitRepository.getHashOfRef( "refs/remotes/origin/" + commit) except OSError as e: if len(commit) == 40: newCommitHash = commit else: raise e break else: raise SyntaxError( "Error reading .subuser.json file, no version constraints matched the current subuser version (" + subuserVersion + ").\n\n" + str(versionConstraints)) updated = not (newCommitHash == self.gitCommitHash) self.gitCommitHash = newCommitHash self.__fileStructure = None return updated