#!/opt/ActivePython-3.3/bin/python3 # -*- coding: utf-8 -*- """@WSTemplateUpdate.py PythonScriptTemplate |------------------------------------------------------------------------------| | Copyright (c) 2008-2014 Perforce Software, Inc. Provided for use as defined | | in the Perforce Consulting Services Agreement. | |------------------------------------------------------------------------------| WSTemplateUpdate.py - Workspace Template Update, a part of the CBD system. Environment Dependencies: This script assumes a complete Perforce environment including a valid, non-expiring ticket in the P4TICKETS file. This script is called by the Perforce server, so the environment should be reliable. """ # Python 2.7/3.3 compatibility. from __future__ import print_function import sys # Python 2.7/3.3 compatibility. if sys.version_info[0] >= 3: from configparser import ConfigParser else: from ConfigParser import ConfigParser import argparse import textwrap import os import re from datetime import datetime import logging from P4 import P4, P4Exception P4INSTANCE = os.getenv ('P4INSTANCE', '1') P4PORT = os.getenv ('P4PORT', 'UNDEFINED_P4PORT_VALUE') P4USER = os.getenv ('P4USER', 'UNDEFINED_P4USER_VALUE') P4CLIENT = os.getenv ('P4CLIENT', 'UNDEFINED_P4CLIENT_VALUE') del os.environ ['P4CONFIG'] DEFAULT_LOG_FILE = '/p4/%s/logs/WSTemplateUpdate.log' % P4INSTANCE DEFAULT_VERBOSITY = 'INFO' LOGGER_NAME = 'WSTemplateUpdateTrigger' DEFAULTS_SECTION = 'Defaults' VERSION = '1.0.0' class Main: """ WSTemplateUpdate """ def __init__(self, *argv): """ Initialization. Process command line argument and initialize logging. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\ NAME WSTemplateUpdate.py VERSION 1.0.0 DESCRIPTION Workspace Template update script. TRIGGER CONFIGURATION This script is intended to be configured as a Perforce server trigger. The entry in the 'Triggers:' table looks like the following: WSTemplateUpdate change-commit //....cbdwst "/p4/common/bin/cbd/triggers/WSTemplateUpdate.py %changelist%" EXIT CODES Zero indicates normal completion. Non-zero indicates an error. '''), epilog="Copyright (c) 2008-2014 Perforce Software, Inc. Provided for use as defined in the Perforce Consulting Services Agreement." ) parser.add_argument('changelist', help="Changelist containing an update to an versioned client spec template (*.cbdwst) file.") parser.add_argument('-n', '--NoOp', action='store_true', help="Take no actions that affect data (\"No Operation\").") parser.add_argument('-L', '--log', default=DEFAULT_LOG_FILE, help="Default: " + DEFAULT_LOG_FILE) parser.add_argument('-v', '--verbosity', nargs='?', const="INFO", default=DEFAULT_VERBOSITY, choices=('DEBUG', 'WARNING', 'INFO', 'ERROR', 'FATAL') , help="Output verbosity level. Default is: " + DEFAULT_VERBOSITY) self.myOptions = parser.parse_args() self.log = logging.getLogger(LOGGER_NAME) self.log.setLevel(self.myOptions.verbosity) #self.logHandler = logging.FileHandler(self.myOptions.log) self.logHandler = logging.FileHandler(self.myOptions.log, mode='w') #Use 'w' for debugging, 'a' for live production usage. # df = datestamp formatter; bf= basic formatter. df = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%m/%d/%Y %H:%M:%S') bf = logging.Formatter('%(levelname)s: %(message)s') self.logHandler.setFormatter(bf) self.log.addHandler (self.logHandler) self.log.debug ("Command Line Options: %s\n" % self.myOptions) if (self.myOptions.NoOp): self.log.info ("Running in NO-OP mode.") # Connect to Perforce def initP4(self): """Connect to Perforce.""" self.p4 = P4() self.p4.prog = LOGGER_NAME self.p4.version = VERSION self.p4.port = P4PORT self.p4.user = P4USER self.p4.client = P4CLIENT self.p4.api_level = 74 ###self.p4.handler = self.logHandler try: self.p4.connect() except P4Exception: self.log.fatal("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) return False return True def get_wst_files(self): """Find all *.cbdwst files submitted in the given changelist. Set set.workspace, self.wst_files[]. Return wst_file_count, the number of *.cbdwst files associated with the changelist. """ wst_file_count = 0 if (not re.match ('\d+$', self.myOptions.changelist)): self.log.fatal ("The value supplied as a changelist number [%s] must be purely numeric." % self.myOptions.changelist) return 0 self.log.info ("Processing changelist %s." % self.myOptions.changelist) self.initP4() try: cData = self.p4.run_describe('-s', self.myOptions.changelist) except P4Exception: self.log.fatal("Failed to describe changlist %s." % self.myOptions.changelist) for e in self.p4.errors: self.log.error("Errors: " + e) for w in self.p4.warnings: self.log.warn("Warnings: " + w) return 0 self.log.debug ("Changelist Data: %s" % cData) self.wst_files = [] index = -1 wst_file_count = 0 for file in cData[0]['depotFile']: index = index + 1 if (re.search ('\.cbdwst$', file)): action = cData[0]['action'][index] rev = cData[0]['rev'][index] # Ignore actions other than add, move/add, branch, edit, and integrate. if (not re.search ('add|branch|edit|integrate', action)): self.log.warn ("Ignored '%s' action on %s#%s." % (action, file, rev)) continue self.wst_files.append(file) wst_file_count = wst_file_count + 1 return wst_file_count def update_template_and_derived_workspaces (self, file): """Update the client spec template on the live server from the versioned template, then update user workspaces derived from the template. """ self.template = re.sub ('^.*\/', '', file, re.IGNORECASE) self.template = re.sub ('\.cbdwst$', '', self.template, re.IGNORECASE) self.log.info ("Updating template %s from %s." % (self.template, file)) clientSearchExpr = '*_%s_*' % self.template clientSearch = self.p4.run_clients ('-e', clientSearchExpr) self.log.debug ("Clients Data: %s." % clientSearch) for clientData in clientSearch: ws = clientData['client'] self.update_derived_workspace (file, self.template, ws) def update_derived_workspace (self, file, template, ws): """Update user workspaces from the versioned template. """ self.log.debug ("Updating user workspace [%s]." % ws) wstFileContents = self.p4.run_print ('-q', file) """ templateClient = wstFileContents[1] if (re.search ('\[\s*NO_CBD\s*\]', wData['Description'], re.IGNORECASE)): self.log.warn ("Found '[NO_CBD]' tag in workspace %s. Skipping update." % ws) return False """ def update_workspace_template (self): """Update stream specs for the given changelist.""" if (self.get_wst_files()): for file in self.wst_files: self.update_template_and_derived_workspaces (file) else: return False if __name__ == '__main__': """ Main Program """ main = Main(*sys.argv[1:]) Main.update_workspace_template(main)