""" Copyright (c) Perforce Software, Inc., 2012. All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. User contributed content on the Perforce Public Depot is not supported by Perforce, although it may be supported by its author. This applies to all contributions even those submitted by Perforce employees. """ #!python # Support methods for CBD # imports import sys, types from P4 import P4, P4Exception, Map import logging import logging.config import os import ntpath, posixpath import re import xml.dom.minidom as minidom from CbdComponent import CbdComponent class Cbd: def __init__(self, logstyle): # constants self.ACCESS_ACTIVE = 'active' self.ACCESS_WRITE = 'write' self.ACCESS_READ = 'read' self.ACCESS_BINARY = 'binary' self.MERGE_CMDS = ['populate', 'merge', 'integrate', 'integ', 'copy', 'move', 'rename'] self.key_config = 'cfgpkg' self.attr_key_config = "attr-" + self.key_config self.cbd_home = os.getenv("CBD_HOME") self.cbd_ws = "cbd_manager" self.logcfgfile = os.path.join(self.cbd_home, "log", "log.cfg") self.logincfgfile = os.path.join(self.cbd_home, "secure", "login.cfg") self.p4 = P4() # globals to read from config files self.p4user = None self.p4passwd = None # log global self.log = None # set up logging and read config data self.initLog(logstyle) self.readLogin() # read login info def readLogin(self): with open(self.logincfgfile, 'r') as f: self.p4user = f.readline().strip('\n') self.p4passwd = f.readline().strip('\n') # set up logging def initLog(self, logstyle): logging.config.fileConfig(self.logcfgfile) self.log = logging.getLogger(logstyle) # make logger available externally def getLogger(self): return self.log def __del__(self): # disconnect if self.p4.connected(): self.p4.disconnect() # Connect to Perforce def initP4(self, p4port): self.p4.prog = "CBD" self.p4.port = p4port self.p4.user = self.p4user self.p4.password = self.p4passwd self.log.debug("Using P4USER=%s" % self.p4user) self.log.debug("Using P4PORT=%s" % p4port) try: self.p4.connect() self.p4.run_login() except P4Exception: self.log.error("Unable to connect to Perforce at P4PORT=%s" % p4port) for e in self.p4.errors: self.log.error("Errors: " + e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) sys.exit(1) def getdata(self, args): try: res = self.p4.run(args) return res except P4Exception: for e in self.p4.errors: self.log.error("Errors: " + e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) def printdata(self, r): if not (isinstance(r, types.NoneType)): for i in range(len(r)): if(self.p4.tagged): dict = r[i] for key in dict.keys(): self.log.debug("%s :: %s" % (key,dict[key])) else: self.log.debug(r[i]) # parse broker arguments # Can read the following from STDIN: # command: user command # brokerListenPort: broker port # brokerTargetPort: server port # clientProg: client program # clientVersion: version of client program # clientProtocol: level of client protocol # apiLevel: level of api protocol # maxLockTime # maxScanRows # maxResults # workspace: name of client workspace # user: name of requesting user # clientIp: IP address of client # proxyIp: IP address of proxy (if any) # cwd: Working dir # argCount: number of arguments to command # Other arguments as necessary (e.g. Arg0, Arg1) def parseBrokerArgs(self): vals = dict() data = sys.stdin.readlines() for arg in data: arg.rstrip('\n') self.log.debug("Read %s" % arg) m = re.match("^(.+?):\s+(.+)$", arg) k = m.group(1) v = m.group(2) vals[k] = v self.log.debug("Arg %s = %s" % (k, v)) return vals def getSpecName(self, ws): return "//spec/client/" + ws + ".p4s#1" def readXmlElement(self, parent, tag): leaves = parent.getElementsByTagName(tag) val = None if leaves and len(leaves) > 0: self.log.debug("Found tag " + tag) leaf = leaves[0] nodes = leaf.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: val = node.data break return val def readComponents(self, config): spec = "//cbd/configurations/" + config + ".xml" self.log.debug("Reading component for configuration %s from file %s" % (config, spec)) self.p4.tagged = False data = self.p4.run("print", "-q", spec) lines = [] for i in range(len(data)): lines.append(data[i]) self.p4.tagged = True self.log.debug("Read config definition %s" % "\n".join(lines)) self.log.debug("Reading components from XML") doc = minidom.parseString("\n".join(lines)) components = doc.getElementsByTagName("component") list = [] for c in components: cc = CbdComponent() cc.depotPath = self.readXmlElement(c, "depotPath") cc.access = self.readXmlElement(c, "access") cc.view = self.readXmlElement(c, "view") cc.clientLocation = self.readXmlElement(c, "clientLocation") list.append(cc) return list def makeWs(self, reqWs, reqConfig, reqUser, reqHost, reqCwd): self.log.info("Making workspace %s for configuration %s" % (reqWs, reqConfig)) try: self.log.debug("Fetching workspace spec...") cspec = self.p4.fetch_client(reqWs) self.log.debug("Default view: %s" % cspec["View"]) cspec["Owner"] = reqUser cspec["Host"] = reqHost cspec["Description"] = "Workspace for configuration %s" % reqConfig cspec["Root"] = reqCwd self.setView(cspec, reqConfig, reqWs) self.log.debug("Saving workspace with view %s..." % cspec["View"]) self.p4.save_client(cspec) self.log.debug("Making notation in spec depot") self.linkWsConfig(reqConfig, reqWs) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error making workspace: %s\"" % ",".join(errors) sys.exit(1) def isOption(self, anArg): self.log.debug("Determining if argument %s is an option" % anArg) return anArg.startswith("-") def violatesAccess(self, anArg, component, cmd): self.log.debug("Checking if operation %s on argument %s violates policy of component %s" % (cmd, anArg, component)) # mainly checks for write access violations if component.access == self.ACCESS_ACTIVE: return False elif component.access == self.ACCESS_WRITE: return (cmd in self.MERGE_CMDS) elif component.access == self.ACCESS_READ: return True elif component.access == self.ACCESS_BINARY: return True def inBranchView(self, reqBranchSpec, toFile, reverseView, component, reqWs, reqCwd): self.log.debug("Checking whether %s is in view of component %s" % (reqBranchSpec, component)) self.log.debug("Branch spec %s limited by toFile %s (reverse = %s)" % (reqBranchSpec, toFile, reverseView)) self.log.debug("Constructing map based on view of component %s" % component) # purposely ignore possible label views, as we want to control the entire component depot path m = Map() m.insert(str(component.depotPath)) self.log.debug("Set map path to %s" % component.depotPath) self.logP4Map(m) self.log.debug("Loading branch view for %s" % reqBranchSpec) bspec = self.p4.fetch_branch(reqBranchSpec) bmap = Map(bspec["View"]) if reverseView: bmap = bmap.reverse() branchMapLines = bmap.rhs() if toFile: bbmap = Map(branchMapLines) branchMapLines = [] branchMapLines.append(bbmap.translate(toFile)) self.log.debug("Using branch map %s" % ",".join(branchMapLines)) includes = False for bline in branchMapLines: includes = includes or m.includes(bline) self.log.debug("%s included in %s: %s" % (reqBranchSpec, component, includes)) return includes def inView(self, anArg, component, reqWs, reqCwd): self.log.debug("Checking whether %s is in view of component %s" % (anArg, component)) self.log.debug("Constructing map based on view of component %s" % component) # purposely ignore possible label views, as we want to control the entire component depot path m = Map() m.insert(str(component.depotPath)) self.log.debug("Set map path to %s" % component.depotPath) self.logP4Map(m) self.log.debug("Finding depot path of %s" % anArg) cspec = self.p4.fetch_client(reqWs) oldClient = self.p4.client oldHost = self.p4.host self.p4.client = cspec["Client"] if "Host" in cspec: self.p4.host = cspec["Host"] fullpath = None # Logic for this check: # 1. starts with '/' = full depot or workspace syntax, full local syntax on *nix # 2. local path = join with current dir using OS-appropriate path separator if anArg.startswith('/'): fullpath = anArg else: self.log.debug("Local path, checking if on Windows or not") if self.isWindowsClient(cspec): fullpath = ntpath.abspath(ntpath.join(reqCwd, anArg)) else: fullpath = posixpath.abspath(posixpath.join(reqCwd, anArg)) self.log.debug("Found full path %s" % fullpath) where = self.p4.run("where", fullpath); self.p4.client = oldClient self.p4.host = oldHost includes = False for wh in where: dpath = wh["depotFile"] self.log.debug("Found depot path %s" % dpath) includes = includes or m.includes(dpath) self.log.debug("%s included in %s: %s" % (anArg, component, includes)) return includes def isWindowsClient(self, cspec): root = cspec["Root"] if '/' in root: return False else: return True def logP4Map(self, m): self.log.debug("Logging map with %s entries" % str(m.count())) for l in m.as_array(): self.log.debug("Map line: %s" % l) def checkMergeByBranch(self, reqWs, reqCmd, reqArgs, reqCwd, reqBranchSpec): self.log.info("Verifying access for operation %s using branch spec %s in workspace %s" % (reqCmd, reqBranchSpec, reqWs)) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Intersecting merge/spec arguments with component access policies") components = self.readComponents(cfg) errors = [] reverseView = False if '-r' in reqArgs: reverseView = True self.log.debug("Using reverse branch view: %s" % reverseView) srcTgtArgs = [] for anArg in reqArgs: if self.isOption(anArg): self.log.debug("Ignoring argument %s" % anArg) else: srcTgtArgs.append(anArg) toFile = None if len(srcTgtArgs) == 1: toFile = srcTgtArgs[0] idx = toFile.find('#') if idx > -1: toFile = toFile[:idx] idx = toFile.find('@') if idx > -1: toFile = toFile[:idx] elif len(srcTgtArgs) != 0: errors.append("Did not understand arguments %s" % ",".join(srcTgtArgs)) print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) self.log.debug("Using target file restriction: %s" % toFile) for c in components: if self.inBranchView(reqBranchSpec, toFile, reverseView, c, reqWs, reqCwd): self.log.debug("Argument %s is in view of component %s" % (reqBranchSpec, c)) if self.violatesAccess(reqBranchSpec, c, reqCmd): errors.append("Component %s has access policy %s that rejects operation %s on %s" % (c.depotPath, c.access, reqCmd, reqBranchSpec)) if len(errors) > 0: print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) sys.exit(1) else: print "action: PASS\n" sys.exit(0) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) sys.exit(1) def checkMergeByFile(self, reqWs, reqCmd, reqArgs, reqCwd): self.log.info("Verifying access for operation %s in workspace %s" % (reqCmd, reqWs)) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Intersecting merge/target arguments with component access policies") components = self.readComponents(cfg) errors = [] srcTgtArgs = [] for anArg in reqArgs: if self.isOption(anArg): self.log.debug("Ignoring argument %s" % anArg) else: srcTgtArgs.append(anArg) if len(srcTgtArgs) != 2: errors.append("Did not understand arguments %s" % ",".join(srcTgtArgs)) print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) tgtFiles = srcTgtArgs[1] self.log.debug("For operation %s found target files %s" % (reqCmd, tgtFiles)) for c in components: if self.inView(tgtFiles, c, reqWs, reqCwd): self.log.debug("Argument %s is in view of component %s" % (tgtFiles, c)) if self.violatesAccess(tgtFiles, c, reqCmd): errors.append("Component %s has access policy %s that rejects operation %s on %s" % (c.depotPath, c.access, reqCmd, tgtFiles)) if len(errors) > 0: print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) sys.exit(1) else: print "action: PASS\n" sys.exit(0) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error verifying operation %s in workspace: %s\"" % (reqCmd, ",".join(errors)) sys.exit(1) def checkEdit(self, reqWs, reqCmd, reqArgs, reqCwd): self.log.info("Verifying access for operation %s in workspace %s" % (reqCmd, reqWs)) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Intersecting edit arguments with component access policies") components = self.readComponents(cfg) errors = [] for anArg in reqArgs: if self.isOption(anArg): self.log.debug("Ignoring argument %s" % anArg) continue for c in components: if self.inView(anArg, c, reqWs, reqCwd): self.log.debug("Argument %s is in view of component %s" % (anArg, c)) if self.violatesAccess(anArg, c, reqCmd): errors.append("Component %s has access policy %s that rejects operation %s on %s" % (c.depotPath, c.access, reqCmd, anArg)) if len(errors) > 0: print "action: REJECT\nmessage: \"Error verifying edits in workspace: %s\"" % ",".join(errors) sys.exit(1) else: print "action: PASS\n" sys.exit(0) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error verifying edits in workspace: %s\"" % ",".join(errors) sys.exit(1) def syncWs(self, reqWs, reqCmd, reqCwd, reqArgs): self.log.info("Syncing workspace %s with arguments %s" % (reqWs, ",".join(reqArgs))) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Making sure workspace view is current") self.switchWs(reqWs, cfg) self.log.debug("Syncing with command %s" % reqCmd) components = self.readComponents(cfg) syncstr = [] for c in components: if c.view == None: syncstr.append(str("%s" % (c.depotPath))) else: syncstr.append(str("%s@%s" % (c.depotPath, c.view))) self.log.debug("Unless other args found, syncing to paths %s" % '\n'.join(syncstr)) optionArgs = [] fileArgs = [] for anArg in reqArgs: if self.isOption(anArg): optionArgs.append(anArg) else: fileArgs.append(anArg) if len(fileArgs) > 0: self.log.debug("Syncing individual arguments") print "action: PASS\nmessage: \"Warning: possible sync outside of configuration view\"" else: self.printRewrite("action: REWRITE") self.printRewrite("command: %s" % reqCmd) for anArg in optionArgs: self.printRewrite("arg: %s" % anArg) for ss in syncstr: self.printRewrite("arg: %s" % ss) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error syncing workspace: %s\"" % ",".join(errors) sys.exit(1) def switchWs(self, reqWs, reqConfig): self.log.info("Switching workspace %s to configuration %s" % (reqWs, reqConfig)) try: self.log.debug("Fetching workspace spec...") cspec = self.p4.fetch_client(reqWs) self.log.debug("Default view: %s" % cspec["View"]) self.setView(cspec, reqConfig, reqWs) self.log.debug("Saving workspace with view %s..." % cspec["View"]) self.p4.save_client(cspec) self.log.debug("Making notation in spec depot") self.linkWsConfig(reqConfig, reqWs) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error switching workspace: %s\"" % ",".join(errors) sys.exit(1) def linkWsConfig(self, reqConfig, reqWs): self.log.info("Recording link between workspace %s and configuration %s" % (reqWs, reqConfig)) self.p4.run("attribute", "-f", "-n", self.key_config, "-v", reqConfig, self.getSpecName(reqWs)) def getWsConfig(self, reqWs): self.log.debug("Getting configuration for workspace %s" % (reqWs)) attrib_out = self.getdata(["fstat", "-Oa", self.getSpecName(reqWs)]) d = attrib_out[0] configPkg = None if self.attr_key_config in d: configPkg = d[self.attr_key_config] self.log.debug("Found config %s for workspace %s" % (configPkg, reqWs)) return configPkg def setView(self, cspec, reqConfig, reqWs): self.log.debug("Setting view...") view = [] components = self.readComponents(reqConfig) for c in components: self.log.debug("Found component %s" % str(c)) cview = c.depotPath self.log.debug("Found component view %s" % cview) view.append(str(cview + " //" + reqWs + "/" + c.clientLocation)) self.log.debug("Setting view to %s" % "\n".join(view)) cspec["View"] = view # make sure nobody is trying to use this with streams if "Stream" in cspec: print "action: REJECT\nmessage: \"Cannot use stream field in workspace tied to configuration\"" sys.exit(1) def updateCfg(self, reqConfigDepotPath, reqUser, reqData): self.log.info("Updating configuration %s with data %s" % (reqConfigDepotPath, reqData)) oldClient = self.p4.client try: self.p4.client = self.cbd_ws self.p4.run("sync", "-f", reqConfigDepotPath); self.p4.run("edit", reqConfigDepotPath); where = self.p4.run("where", reqConfigDepotPath); lpath = where[0]["path"] self.log.debug("Writing new config to path %s" % lpath) with open(lpath, 'w') as f: f.write(reqData) self.p4.run_submit("-d", "Updating configuration", reqConfigDepotPath); self.p4.client = oldClient except P4Exception: self.p4.client = oldClient errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error updating configuration: %s\"" % ",".join(errors) sys.exit(1) def deleteCfg(self, reqConfigDepotPath, reqUser): self.log.info("Deleting configuration %s" % reqConfigDepotPath) oldClient = self.p4.client try: self.p4.client = self.cbd_ws self.p4.run("sync", "-f", reqConfigDepotPath); self.p4.run("delete", reqConfigDepotPath); self.p4.run_submit("-d", "Deleting configuration", reqConfigDepotPath); self.p4.client = oldClient except P4Exception: self.p4.client = oldClient errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error deleting configuration: %s\"" % ",".join(errors) sys.exit(1) def addCfg(self, reqConfig, reqUser, reqData): self.log.info("Adding configuration %s with data %s" % (reqConfig, reqData)) oldClient = self.p4.client try: self.p4.client = self.cbd_ws reqConfigDepotPath = "//cbd/configurations/" + reqConfig + ".xml" where = self.p4.run("where", reqConfigDepotPath); lpath = where[0]["path"] self.log.debug("Writing new config to path %s" % lpath) with open(lpath, 'w') as f: f.write(reqData) self.p4.run("add", lpath); self.p4.run_submit("-d", "Adding configuration", lpath); self.p4.client = oldClient except P4Exception: self.p4.client = oldClient errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error adding configuration: %s\"" % ",".join(errors) sys.exit(1) def printRewrite(self, msg): self.log.debug("Rewrite: %s" % msg) print msg def checkUnshelve(self, reqWs, reqArgs, reqCwd, reqShelf): self.log.info("Verifying unshelving from shelf %s into workspace %s" % (reqShelf, reqWs)) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Reading files for shelf %s" % reqShelf) describeData = self.p4.run("describe", "-s", "-S", reqShelf) shelfDesc = describeData[0] shelfFiles = shelfDesc["depotFile"] shelfActions = shelfDesc["action"] self.log.debug("Found shelf %s with %s files" % (reqShelf, str(len(shelfFiles)))) self.log.debug("Intersecting shelf arguments with component access policies") components = self.readComponents(cfg) errors = [] for ii in range(len(shelfFiles)): sFile = shelfFiles[ii] sCmd = shelfActions[ii] for c in components: if self.inView(sFile, c, reqWs, reqCwd): self.log.debug("Shelved file %s is in view of component %s" % (sFile, c)) if self.violatesAccess(sFile, c, sCmd): errors.append("Component %s has access policy %s that rejects operation %s on %s" % (c.depotPath, c.access, sCmd, sFile)) if len(errors) > 0: print "action: REJECT\nmessage: \"Cannot unshelve %s in workspace: %s\"" % (reqShelf, ",".join(errors)) sys.exit(1) else: print "action: PASS\n" sys.exit(0) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Cannot unshelve %s in workspace: %s\"" % (reqShelf, ",".join(errors)) sys.exit(1) def reconcileWs(self, reqWs, reqCmd, reqCwd, reqArgs): self.log.info("Reconciling workspace %s with arguments %s" % (reqWs, ",".join(reqArgs))) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) self.log.debug("Making sure workspace view is current") self.switchWs(reqWs, cfg) self.log.debug("Reconciling with command %s" % reqCmd) components = self.readComponents(cfg) reconcilestr = [] for c in components: if c.access == self.ACCESS_ACTIVE or c.access == self.ACCESS_WRITE: reconcilestr.append(str("%s" % (c.depotPath))) self.log.debug("Unless other args found, reconciling to paths %s" % '\n'.join(reconcilestr)) optionArgs = [] fileArgs = [] for anArg in reqArgs: if self.isOption(anArg): optionArgs.append(anArg) else: fileArgs.append(anArg) if len(fileArgs) > 0: self.log.debug("Reconciling individual arguments") print "action: PASS\nmessage: \"Warning: possible reconcile outside of configuration view\"" else: self.printRewrite("action: REWRITE") self.printRewrite("command: %s" % reqCmd) for anArg in optionArgs: self.printRewrite("arg: %s" % anArg) for ss in reconcilestr: self.printRewrite("arg: %s" % ss) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Error reconciling workspace: %s\"" % ",".join(errors) sys.exit(1) def checkSubmit(self, reqWs, reqArgs, reqCwd, reqChange): self.log.info("Verifying submitting from workspace %s" % (reqWs)) try: self.log.debug("Fetching workspace spec...") cfg = self.getWsConfig(reqWs) if cfg == None: self.log.debug("No configuration for workspace %s " % reqWs) print "action: PASS\n" sys.exit(0) if reqChange == None: self.log.debug("No changelist specified") print "action: REJECT\nmessage: \"Must submit using numbered changelist\"" sys.exit(1) self.log.debug("Reading files for changelist %s" % reqChange) describeData = self.p4.run("describe", "-s", reqChange) changeDesc = describeData[0] changeFiles = changeDesc["depotFile"] changeActions = changeDesc["action"] self.log.debug("Found change %s with %s files" % (reqChange, str(len(changeFiles)))) self.log.debug("Intersecting change arguments with component access policies") components = self.readComponents(cfg) errors = [] for ii in range(len(changeFiles)): sFile = changeFiles[ii] sCmd = changeActions[ii] fileFound = False for c in components: if self.inView(sFile, c, reqWs, reqCwd): fileFound = True self.log.debug("Pending file %s is in view of component %s" % (sFile, c)) if self.violatesAccess(sFile, c, sCmd): errors.append("Component %s has access policy %s that rejects operation %s on %s" % (c.depotPath, c.access, sCmd, sFile)) if not fileFound: errors.append("%s not in view specified by configuration %s" % (sFile, cfg)) if len(errors) > 0: print "action: REJECT\nmessage: \"Cannot submit %s in workspace: %s\"" % (reqChange, ",".join(errors)) sys.exit(1) else: print "action: PASS\n" sys.exit(0) except P4Exception: errors = [] for e in self.p4.errors: self.log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) errors.append(w) print "action: REJECT\nmessage: \"Cannot submit %s in workspace: %s\"" % (reqChange, ",".join(errors)) sys.exit(1)
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#24 | 19425 | C. Thomas Tyler |
Fixed issue with sync of a deep depot (StreamDepth>1) when stream spec has import entries. This causes Test 43 to pass. |
||
#23 | 19352 | C. Thomas Tyler |
Added utility script blog.py. Updated copyright year. |
||
#22 | 19295 | C. Thomas Tyler |
Changed logging 'mode' parameter to filemode. Need to look further into this, as 'mode=a' has worked in some environments, but it seems filemode='a' is needed in the test environment. |
||
#21 | 19271 | C. Thomas Tyler | Switched back to append-mode logging. | ||
#20 | 19253 | C. Thomas Tyler | Routine merge-down from main to dev. | ||
#19 | 16355 | C. Thomas Tyler |
Routine Merge Down to dev from main using: p4 -s merge -b perforce_software-cbd-dev |
||
#18 | 15630 | C. Thomas Tyler |
Fix for users getting 'ERROR: CBD was not able to translate path ' errors. This occured with sync commands from done specify the local OS syntax path from the root of a workspace associated with a 'complex' stream spec. |
||
#17 | 15358 | C. Thomas Tyler |
Tweaked test prep to use an uppercase drive letter in creation of test workspace earl_jams, to better test for Windows drive letter issues (Workshiop job job000330). Added new test for job000330, which initially failed (a valid repro). With changes in Cbd.py, it now passes. |
||
#16 | 15279 | C. Thomas Tyler |
Fixed issue recognizing sync from Linux. Fixed 'p4 sync ...' from root of workspace. Silenced 'is a complex relative path' log warning for simple '...' path. |
||
#15 | 15274 | C. Thomas Tyler |
Merge Down of cbd to dev from main using: p4 merge -b perforce_software-cbd-dev |
||
#14 | 15177 | C. Thomas Tyler |
Fixed various path translation issues that could result in a bad CBD sync. Now gives a useful error message if path translation fails, and aborts the sync. |
||
#13 | 15154 | C. Thomas Tyler |
Rewrote path translation logic. Now, rather than running 'p4 where' and parsing the output, we use P4Python's nifty P4.Map() feature to do comparisons locally. This has the added benefit for making at faster and more robust, as the old logic had issue with running 'p4 where' on behalf of users with locked clients or with the 'Host' field set. This also now handles paths in Workspace Syntax and Local OS syntax better, including absolute local paths, simple relative paths, and even some complex relative paths. |
||
#12 | 15039 | C. Thomas Tyler |
Merge Down CBD to dev from main to pick up test suite improvements and an exception bug fix in Cbd.py. |
||
#11 | 15008 | C. Thomas Tyler | Merge down from CBD mainline. | ||
#10 | 14947 | C. Thomas Tyler |
Adjusted file type, removing '+x', to indicate that Cbd.py is imported, not directly executed. No content changes. |
||
#9 | 14839 | C. Thomas Tyler | Updated to latest CBD from dev box. | ||
#8 | 14198 | C. Thomas Tyler |
Got Workshop up to date with latest version of CBD developed elsewhere. Added CbdDev.py to illustrate enabling testing a newer version on a live server, as a supplement to the test suite. This comes with supporting scripts wssync.dev.(sh,py) Added cmd_trig_by_auth.pl, a technology sample script. Not used presently. |
||
#7 | 13804 | C. Thomas Tyler | Fixed copy/paste errors. | ||
#6 | 13775 | C. Thomas Tyler |
Simplified logging and added support for logging with multiple SDP instances. |
||
#5 | 13773 | C. Thomas Tyler |
Added support for handling import+ entries. Fixed bug where excess keys were not cleaned up, e.g. if a stream spec was modified to delete an import entry. Corrected text in debug output. |
||
#4 | 11365 | C. Thomas Tyler |
Various enhancements and bug fixes in Cbd.py. Removed unneeded CbdComponent.py. |
||
#3 | 11360 | C. Thomas Tyler |
chmod +x. No content changes. |
||
#2 | 11348 | C. Thomas Tyler | Uploaded CBD scripts as presented at Merge 2014. | ||
#1 | 11347 | C. Thomas Tyler |
Populate //guest/perforce_software/cbd/dev/... from //guest/perforce_software/cbd/main/.... |
||
//guest/perforce_software/cbd/main/scripts/Cbd.py | |||||
#1 | 11196 | C. Thomas Tyler |
Added CBD sample logic. It was developed by Randy DeFauw as a PoC, and mentioned at the Perforce MERGE 2013 User Conference. This PoC explores a broad spectrum of CBD challenges, and thus is complex. Actual production implementations derived can be simpler. This implementation works with Classic Perforce only; support for Streams is possible. |
||
//guest/randy_defauw/cbd/scripts/Cbd.py | |||||
#1 | 8278 | Randy DeFauw | Import component development tool kit |