Source code for subuserlib.classes.repository

# -*- coding: utf-8 -*-

"""
A repository is a collection of ``ImageSource`` s which are published in a git repo.
"""

#external imports
import os
import shutil
import json
#internal imports
from subuserlib.classes.userOwnedObject import UserOwnedObject
from subuserlib.classes.imageSource import ImageSource
from subuserlib.classes.describable import Describable
from subuserlib.classes.gitRepository import GitRepository
from subuserlib.classes.fileStructure import BasicFileStructure
import subuserlib.version

[docs]class Repository(dict,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.__lastGitCommitHash = gitCommitHash self.__temporary = temporary self.__sourceDir = sourceDir self.__fileStructure = None UserOwnedObject.__init__(self,user) self.__gitRepository = GitRepository(user,self.getRepoPath()) if not self.isPresent(): self.updateSources(initialUpdate=True) self.__repoConfig = self.loadRepoConfig() if self.isPresent(): self.loadImageSources()
[docs] def getName(self): return self.__name
[docs] def getURI(self): if self.isLocal(): return self.getSourceDir() else: return self.getGitOriginURI()
[docs] def getSourceDir(self): return self.__sourceDir
[docs] def getGitOriginURI(self): return self.__gitOriginURI
[docs] def getGitRepository(self): return self.__gitRepository
[docs] def getFileStructure(self): if self.__fileStructure is None: if self.isLocal(): self.__fileStructure = BasicFileStructure(self.getSourceDir()) else: if self.getGitCommitHash() is None: self.updateGitCommitHash() self.__fileStructure = self.getGitRepository().getFileStructureAtCommit(self.getGitCommitHash()) return self.__fileStructure
[docs] def getDisplayName(self): """ How should we refer to this repository when communicating with the user? """ if self.isTemporary(): if self.isLocal(): return self.getSourceDir() else: return self.getGitOriginURI() else: return self.getName()
[docs] def describe(self): print("Repository: "+self.getDisplayName()) print("------------") if self.isLocal(): print("Is a local(non-git) repository.") if not self.isTemporary(): print("Located at: " + self.getRepoPath()) if self.isTemporary(): print("Is a temporary repository.") else: if not self.isLocal(): print("Cloned from: "+self.getGitOriginURI()) print("Currently at commit: "+self.getGitCommitHash())
[docs] def getSortedList(self): """ Return a list of image sources sorted by name. """ return list(sorted(self.values(),key=lambda imageSource:imageSource.getName()))
[docs] def getRepoPath(self): """ Get the path of the repo's sources on disk. """ if self.isLocal(): return self.getSourceDir() else: return os.path.join(self.getUser().getConfig()["repositories-dir"],self.getName())
[docs] 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.getFileStructure().exists("./.subuser.json"): configFileContents = self.getFileStructure().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
[docs] def getRepoConfig(self): return self.__repoConfig
[docs] def getImageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ return os.path.join(self.getRepoPath(),self.getRelativeImageSourcesDir())
[docs] def getRelativeImageSourcesDir(self): """ Get the path of the repo's subuser root on disk on the host. """ repoConfig = self.getRepoConfig() if repoConfig and "image-sources-dir" in repoConfig: return repoConfig["image-sources-dir"] else: return "./"
[docs] def isTemporary(self): return self.__temporary
[docs] def isInUse(self): """ Are there any installed images or subusers from this repository? """ for _,installedImage in self.getUser().getInstalledImages().items(): if self.getName() == installedImage.getSourceRepoId(): return True for _,subuser in self.getUser().getRegistry().getSubusers().items(): try: if self.getName() == subuser.getImageSource().getRepository().getName(): return True except subuserlib.classes.subuser.NoImageSourceException: pass return False
[docs] def isLocal(self): if self.__sourceDir: return True return False
[docs] def removeGitRepo(self): """ Remove the downloaded git repo associated with this repository from disk. """ if not self.isLocal(): shutil.rmtree(self.getRepoPath())
[docs] def isPresent(self): """ Returns True if the repository's files are present on the system. (Cloned or local) """ return os.path.exists(self.getRepoPath())
[docs] 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.getUser().getRegistry().log("Cloning repository "+self.getName()+" from "+self.getGitOriginURI()) if self.getGitRepository().clone(self.getGitOriginURI()) != 0: self.getUser().getRegistry().log("Clone failed.") return else: new = False try: self.getGitRepository().run(["fetch","--all"]) except Exception: # For some reason, git outputs normal messages to stderr. pass if self.updateGitCommitHash(): if not new: self.getUser().getRegistry().logChange("Updated repository "+self.getDisplayName()) if not initialUpdate: self.loadImageSources()
[docs] def serializeToDict(self): """ Return a dictionary which describes the image sources available in this repository. """ imageSourcesDict = {} for name,imageSource in self.items(): imageSourcesDict[name] = {} return imageSourcesDict
[docs] def loadImageSources(self): """ Load ImageSources from disk into memory. """ imageNames = self.getFileStructure().lsFolders(self.getRelativeImageSourcesDir()) imageNames = [os.path.basename(path) for path in imageNames] for imageName in imageNames: imageSource = ImageSource(self.getUser(),self,imageName) if self.getFileStructure().exists(imageSource.getRelativePermissionsFilePath()): self[imageName] = imageSource if self.getRepoConfig() is not None and "explicit-image-sources" in self.getRepoConfig(): for imageName,config in self.getRepoConfig()["explicit-image-sources"].items(): assert config is not None self[imageName] = ImageSource(self.getUser(),self,imageName,explicitConfig = config)
[docs] 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 newCommitHash = self.getGitRepository().getHashOfRef("refs/remotes/origin/master") # First we check for version constraints on the repository. if self.getGitRepository().getFileStructureAtCommit("master").exists("./.subuser.json"): configFileContents = self.getGitRepository().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.getUser()) 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.getGitRepository().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.__lastGitCommitHash) self.__lastGitCommitHash = newCommitHash self.__fileStructure = None return updated
[docs] def getGitCommitHash(self): return self.__lastGitCommitHash