WSTemplateUpdate.py #2

  • //
  • guest/
  • perforce_software/
  • cbd/
  • main/
  • triggers/
  • WSTemplateUpdate.py
  • View
  • Commits
  • Open Download .zip Download (8 KB)
#!/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)
# Change User Description Committed
#3 15273 C. Thomas Tyler Copy Up using 'p4 copy -r -b perforce_software-cbd-ntx64'.
Stabilization changes.
Test suite enhancements.
#2 15009 C. Thomas Tyler Promoted CBD development work to main from dev.
#1 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.
//guest/perforce_software/cbd/dev/triggers/WSTemplateUpdate.py
#1 11355 C. Thomas Tyler Added CBD triggers.