Cbd.py #1

  • //
  • guest/
  • perforce_software/
  • cbd/
  • main/
  • scripts/
  • Cbd.py
  • View
  • Commits
  • Open Download .zip Download (34 KB)
"""
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
#21 19429 C. Thomas Tyler Released CBD/MultiArch/2016.2/19425 (2016/05/17).
#20 19355 C. Thomas Tyler Released CBD/MultiArch/2016.2/19352 (2016/05/10).
Copy Up using 'p4 copy -r -b perforce_software-cbd-dev'.

No funtional changes to CBD behaviors.
#19 19298 C. Thomas Tyler Released CBD/MultiArch/2016.1/19295 (2016/05/09).
#18 19292 C. Thomas Tyler Copy Up from dev to main.

Released CBD/MultiArch/2016.1/19285 (2016/05/07).

* Added 'Version' file and Release Notes.
* Added test suite documentation.
* Tweaks to CBD logging, back to using append mode.
#17 16706 C. Thomas Tyler Tweaked to remove mode paramter.
#16 16705 C. Thomas Tyler Set to use SDP standard Python/P4Python.
#15 16702 C. Thomas Tyler Configured to ensure python3 is used.
#14 16353 C. Thomas Tyler Support for 'import+' was partially broken.
 Fixed!
#13 15631 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.

Copy Up to main from dev.
#12 15360 C. Thomas Tyler Copy Up using 'p4 copy -r -b perforce_software-cbd-dev'.

Added new test for job000330, which initially failed (a valid repro).
Then fixed it.
#11 15282 C. Thomas Tyler Copy Up using 'p4 copy -r -b perforce_software-cbd-dev'.
#10 15273 C. Thomas Tyler Copy Up using 'p4 copy -r -b perforce_software-cbd-ntx64'.
Stabilization changes.
Test suite enhancements.
#9 15179 C. Thomas Tyler Copy Up to main from dev for CBD to pick up bug fixes, using:
p4 copy -r -b perforce_software-cbd-dev
#8 15158 C. Thomas Tyler Copy Up from dev to main for CBD, using:
p4 copy -r -b perforce_software-cbd-dev
#7 15022 C. Thomas Tyler Copy-Up to main from ntx64:
* Fixed Cbd.py issue with an exception handling bug for Host field.
* Rebased CbdDev.py to Cbd.py
* Simplified test operations.
* Fixed a cosmetic typo in test doc/data.
#6 15009 C. Thomas Tyler Promoted CBD development work to main from dev.
#5 13835 C. Thomas Tyler Added Windows support for server-side components.
These changes should not interfere with Linux operation.
Includes:
* Sample Windows broker config file.
* Windows batch file wrapper scripts.
* Code changes to support minor variations for
Windows and Linux SDP.
* Some defensive coding to avoid an exception to avoid
processing a Classic workspace.
#4 13805 C. Thomas Tyler Copy Up from to main from dev.
Completed support for 'import+' handling.
Simplified logging.
Fixed bug where CBD keys were not cleanly updated in case of a removal of
an import.
Fixed internal doc bugs.
#3 11366 C. Thomas Tyler Promoted CBD from dev to main.
#2 11356 C. Thomas Tyler Promotion from Dev Branch.

       What's included:
       * CBD scripts for Streams as demonstrated at Merge 2014.
       * Deletion of files from the original PoC that aren't needed.

       What's coming later, still work in progress on the dev branch:
       * Documentation.
       * Test Suite with complete Vagrant-based Test Environment.
       * CBD scripts for Classic.
#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