import sys import os.path import subprocess import shutil import docker from constants import constants from Logger import Logger from CommandUtils import CommandUtils class Sandbox(object): def __init__(self, logger): self.logger = logger def create(self, name): pass def destroy(self): pass def run(self, logfile, logfn): pass def put(self, src, dest): pass class Chroot(Sandbox): def __init__(self, logger): Sandbox.__init__(self, logger) self.chrootID = None self.prepareBuildRootCmd = "./prepare-build-root.sh" self.runInChrootCommand = ("./run-in-chroot.sh " + constants.sourcePath + " " + constants.rpmPath) self.chrootCmdPrefix = None def getPath(self): return self.chrootID def create(self, chrootName): if self.chrootID: raise Exception("Unable to create: " + chrootName + ". Chroot is already active: " + self.chrootID) chrootID = constants.buildRootPath + "/" + chrootName if os.path.isdir(chrootID): self._destroy(chrootID) # need to add timeout for this step # http://stackoverflow.com/questions/1191374/subprocess-with-timeout cmdUtils = CommandUtils() returnVal = cmdUtils.runCommandInShell("mkdir -p " + chrootID) if returnVal != 0: raise Exception("Unable to create chroot: " + chrootID + ". Unknown error.") self.logger.debug("Created new chroot: " + chrootID) cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/dev") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/etc") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/proc") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/run") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/sys") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/tmp") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/publishrpms") cmdUtils.runCommandInShell("mkdir -p " + chrootID + "/publishxrpms") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath) cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/RPMS") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/SRPMS") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/SOURCES") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/SPECS") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/LOGS") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/BUILD") cmdUtils.runCommandInShell("mkdir -p " + chrootID + constants.topDirPath + "/BUILDROOT") prepareChrootCmd = self.prepareBuildRootCmd + " " + chrootID returnVal = cmdUtils.runCommandInShell(prepareChrootCmd, logfn=self.logger.debug) if returnVal != 0: self.logger.error("Prepare build root script failed.Unable to prepare chroot.") raise Exception("Prepare build root script failed") if os.geteuid() == 0: cmdUtils.runCommandInShell("mount --bind " + constants.rpmPath + " " + chrootID + constants.topDirPath + "/RPMS") cmdUtils.runCommandInShell("mount --bind " + constants.sourceRpmPath + " " + chrootID + constants.topDirPath + "/SRPMS") cmdUtils.runCommandInShell("mount -o ro --bind " + constants.prevPublishRPMRepo + " " + chrootID + "/publishrpms") cmdUtils.runCommandInShell("mount -o ro --bind " + constants.prevPublishXRPMRepo + " " + chrootID + "/publishxrpms") self.logger.debug("Successfully created chroot:" + chrootID) self.chrootID = chrootID self.chrootCmdPrefix = self.runInChrootCommand + " " + chrootID + " " def destroy(self): self._destroy(self.chrootID) self.chrootID = None def _destroy(self, chrootID): if not chrootID: return self.logger.debug("Deleting chroot: " + chrootID) self._unmountAll(chrootID) self._removeChroot(chrootID) def run(self, cmd, logfile=None, logfn=None): self.logger.debug("Chroot.run() cmd: " + self.chrootCmdPrefix + cmd) cmd = cmd.replace('"', '\\"') return CommandUtils.runCommandInShell(self.chrootCmdPrefix + cmd, logfile, logfn) def put(self, src, dest): shutil.copy2(src, self.chrootID + dest) def _removeChroot(self, chrootPath): cmd = "rm -rf " + chrootPath process = subprocess.Popen("%s" %cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) retval = process.wait() if retval != 0: raise Exception("Unable to remove files from chroot " + chrootPath) def unmountAll(self): self._unmountAll(self.chrootID) def _unmountAll(self, chrootID): listmountpoints = self._findmountpoints(chrootID) if listmountpoints is None: return True for mountpoint in listmountpoints: cmd = "umount " + mountpoint process = subprocess.Popen("%s" %cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) retval = process.wait() if retval != 0: raise Exception("Unable to unmount " + mountpoint) def _findmountpoints(self, chrootPath): if not chrootPath.endswith("/"): chrootPath = chrootPath + "/" cmd = "mount | grep " + chrootPath + " | cut -d' ' -s -f3" process = subprocess.Popen("%s" %cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) retval = process.wait() if retval != 0: raise Exception("Unable to find mountpoints in chroot") mountpoints = process.communicate()[0].decode() mountpoints = mountpoints.replace("\n", " ").strip() if mountpoints == "": self.logger.debug("No mount points found") return None listmountpoints = mountpoints.split(" ") sorted(listmountpoints) listmountpoints.reverse() return listmountpoints class Container(Sandbox): def __init__(self, logger): Sandbox.__init__(self, logger) self.containerID = None self.dockerClient = docker.from_env(version="auto") def getID(self): return self.containerID.short_id def create(self, containerName): containerID = None mountVols = { constants.prevPublishRPMRepo: {'bind': '/publishrpms', 'mode': 'ro'}, constants.prevPublishXRPMRepo: {'bind': '/publishxrpms', 'mode': 'ro'}, constants.tmpDirPath: {'bind': '/tmp', 'mode': 'rw'}, constants.rpmPath: {'bind': constants.topDirPath + "/RPMS", 'mode': 'rw'}, constants.sourceRpmPath: {'bind': constants.topDirPath + "/SRPMS", 'mode': 'rw'}, # constants.logPath: {'bind': constants.topDirPath + "/LOGS", 'mode': 'rw'}, # Prepare an empty chroot environment to let docker use the BUILD folder. # This avoids docker using overlayFS which will cause make check failure. # chroot.getPath() + constants.topDirPath + "/BUILD": {'bind': constants.topDirPath + "/BUILD", # 'mode': 'rw'}, constants.dockerUnixSocket: {'bind': constants.dockerUnixSocket, 'mode': 'rw'} } containerName = containerName.replace("+", "p") try: oldContainerID = self.dockerClient.containers.get(containerName) if oldContainerID is not None: oldContainerID.remove(force=True) except docker.errors.NotFound: try: sys.exc_clear() except: pass #TODO: Is init=True equivalent of --sig-proxy? privilegedDocker = False cap_list = ['SYS_PTRACE'] # if packageName in constants.listReqPrivilegedDockerForTest: # privilegedDocker = True containerID = self.dockerClient.containers.run(constants.buildContainerImage, detach=True, cap_add=cap_list, # privileged=privilegedDocker, privileged=False, name=containerName, network_mode="host", volumes=mountVols, command="tail -f /dev/null") if not containerID: raise Exception("Unable to start Photon build container for task " + containerTaskName) self.logger.debug("Successfully created container:" + containerID.short_id) self.containerID = containerID def destroy(self): self.containerID.remove(force=True) self.containerID = None def run(self, cmd, logfile=None, logfn=None): result = self.containerID.exec_run(cmd) if result.output: if logfn: logfn(result.output.decode()) elif logfile: with open(logfile, "w") as f: f.write(result.output.decode()) f.flush() return result.exit_code def put(self, src, dest): copyCmd = "docker cp " + src + " " + self.containerID.short_id + ":" + dest CommandUtils.runCommandInShell(copyCmd)