CheckJobEditTrigger.py #2

  • //
  • guest/
  • perforce_software/
  • sdp/
  • dev/
  • Unsupported/
  • Samples/
  • triggers/
  • CheckJobEditTrigger.py
  • View
  • Commits
  • Open Download .zip Download (8 KB)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
# ------------------------------------------------------------------------------

# tag::includeManual[]
"""
NAME:
    CheckJobEditTrigger.py

DESCRIPTION:
    This trigger is intended for use with P4DTG (Defect Tracking Replication) installations.

    It can (optionally as configured):

     1.  Prevent creation of new jobs in Perforce by anyone other than the
         replication user.
     2   Prevent modification of read-only fields of jobs in Perforce by anyone
         other than the replication user.
     3.  Create newly mirrored jobs using the same name as that in the defect
         tracker.

    To install, add a line to your Perforce triggers table like the following:

        job_save_check form-in job "python /p4/common/bin/triggers/CheckJobEditTrigger.py -p %serverport% -u perforce %user% %formfile% "

    or (if server is standard SDP and has appropriate environment defaults for P4PORT and P4USER):

        job_save_check form-in job "python /p4/common/bin/triggers/CheckJobEditTrigger.py %user% %formfile% "

    You may need to provide the full path to python executable, or edit the path to the trigger.

    Also, don't forget to make the file executable, and set the configuration variables below.

    To configure, read and modify the following lines up to the comment that
    reads "END OF CONFIGURATION BLOCK".  You may also need to modify the
    definition of which fields constitute a new job based on your jobspec.  This
    is in the allowed_job() function.
"""
# end::includeManual[]

# Python 2.7/3.3 compatibility.
from __future__ import print_function

import sys
import re
import P4Triggers
import shutil
import os

trigger_name = os.path.basename(os.path.splitext(__file__)[0])

######################################################################
# tag::includeManual[]
# CONFIGURATION BLOCK

# The error messages we give to the user
MSG_CANT_CREATE_JOBS = """

You are not allowed to create new jobs!
Please create or modify the JIRA issue and let changes be replicated.
"""
MSG_CANT_CHANGE_FIELDS = """

You have changed one or more read-only job fields:
"""

# The list of writeable fields that users can change.
# Changes to any other fields are rejected.
# Please validate this against your jobspec.
WRITEABLE_FIELDS = ["Status", "Date"]

# Replicator user - this user is allowed to change fields
REPLICATOR_USER = "p4dtg"
JIRA_USER = "jira"


# END OF CONFIGURATION BLOCK
# end::includeManual[]
######################################################################

class CheckJobEditTrigger(P4Triggers.P4Trigger):
    """See module doc string for details"""

    def __init__(self, *args, **kwargs):
        P4Triggers.P4Trigger.__init__(self, **kwargs)
        self.parse_args(__doc__, args)

    def add_parse_args(self, parser):
        """Specific args for this trigger - also calls super class to add common trigger args"""
        parser.add_argument('-r', '--rename-job', default=False, action="store_true",
                            help="Whether to rename-jobs to P4DTG name. Default: False")
        parser.add_argument('--prefix', default="",
                            help="Prefix for jobnames when renaming. Default: ''")
        parser.add_argument('user', help="User carrying out the command - %%user%% argument from triggers entry.")
        parser.add_argument('formfile',
                            help="Formfile containing job definition - %%formfile%% argument from triggers entry.")
        super(CheckJobEditTrigger, self).add_parse_args(parser)

    def find_date_field(self, job_spec):
        """Find's the specific field in job spec which is a date modified field.
        If this field is blank it indicates the job is a new job.
        :param job_spec:
        """
        self.logger.debug("Jobspec: %s" % str(job_spec))
        # Format:
        # 104 Date date 20 always
        for f in job_spec["Fields"]:
            parts = f.split()
            if parts[2] == "date" and parts[4] == "always":
                self.logger.debug("Date field: %s" % parts[1])
                return parts[1]
        return None

    def fields_in_error(self, new_job, orig_job):
        """Return the names of all fields that have been modified when they shouldn't have been"""
        fields_in_error = []
        all_keys = set(orig_job.keys())
        all_keys = all_keys.union(new_job.keys())
        for key in all_keys:
            if key in WRITEABLE_FIELDS:
                continue
            if key in orig_job and key in new_job and orig_job[key] != new_job[key]:
                orig_val = new_val = None
                if key in orig_job:
                    orig_val = orig_job[key].replace("\r\n", "\n")
                    orig_val = re.sub("\s+", " ", orig_val)
                if key in new_job:
                    new_val = new_job[key].replace("\r\n", "\n")
                    new_val = re.sub("\s+", " ", new_val)
                if orig_val != new_val:
                    self.logger.debug("Orig_val: '%s', new_val: '%s'" % (orig_val, new_val))
                    fields_in_error.append(key)
        return fields_in_error

    def run(self):
        """Runs trigger"""
        try:
            self.logger.debug("%s: firing" % trigger_name)
            self.setupP4()
            self.p4.connect()
            # Force the jobspec to be loaded into P4Python
            # This is required in order to be able to use parse_job() which otherwise
            # doesn't understand the jobspec (defaults to standard one)
            self.p4.fetch_job()
            with open(self.options.formfile, 'r') as f:
                contents = f.read()
            self.logger.debug("New job:\n%s" % contents)
            new_job = self.p4.parse_job(contents)
            self.logger.debug("Parsed: %s" % (str(new_job)))

            jobname = "new"
            if "Job" in new_job:
                jobname = new_job["Job"]

            if not self.options.user in [REPLICATOR_USER, JIRA_USER]:
                if jobname == "new":
                    self.message(MSG_CANT_CREATE_JOBS)
                    return 1

                # Search for the field set when job created as it also indicates a new job when not present
                job_spec = self.p4.fetch_jobspec()
                date_field = self.find_date_field(job_spec)
                if date_field and date_field not in new_job:
                    self.message(MSG_CANT_CREATE_JOBS)
                    return 1

                orig_job = self.p4.fetch_job(jobname)
                self.logger.debug("Original: %s" % (str(orig_job)))

                fields_in_error = self.fields_in_error(new_job, orig_job)
                if fields_in_error:
                    msg = MSG_CANT_CHANGE_FIELDS + str(fields_in_error) + "\n\n"
                    self.message(msg)
                    return 1
            elif jobname == "new" and self.options.rename_job and "P4DTG_DTISSUE" in new_job:
                # We want to change the jobname by updating formfile
                dt_name = "%s%s" % (self.options.prefix, new_job["P4DTG_DTISSUE"])
                new_job["Job"] = dt_name
                contents = self.p4.format_job(new_job)
                tempfile = "%s~" % self.options.formfile
                shutil.move(self.options.formfile, tempfile)
                with open(self.options.formfile, 'w') as f:
                    f.write(contents)
                os.remove(tempfile)
        except Exception:
            return self.reportException()

        return 0


if __name__ == '__main__':
    """ Main Program"""
    trigger = CheckJobEditTrigger(*sys.argv[1:])
    sys.exit(trigger.run())
# Change User Description Committed
#2 26681 Robert Cowham Removing Deprecated folder - if people want it they can look at past history!
All functions have been replaced with standard functionality such as built in LDAP,
or default change type.
Documentation added for the contents of Unsupported folder.
Changes to scripts/triggers are usually to insert tags for inclusion in ASCII Doctor docs.
#1 26652 Robert Cowham This is Tom's change:

Introduced new 'Unsupported' directory to clarify that some files
in the SDP are not officially supported. These files are samples for
illustration, to provide examples, or are deprecated but not yet
ready for removal from the package.

The Maintenance and many SDP triggers have been moved under here,
along with other SDP scripts and triggers.

Added comments to p4_vars indicating that it should not be edited
directly. Added reference to an optional site_global_vars file that,
if it exists, will be sourced to provide global user settings
without needing to edit p4_vars.

As an exception to the refactoring, the totalusers.py Maintenance
script will be moved to indicate that it is supported.

Removed settings to support long-sunset P4Web from supported structure.

Structure under new .../Unsupported folder is:
   Samples/bin             Sample scripts.
   Samples/triggers        Sample trigger scripts.
   Samples/triggers/tests  Sample trigger script tests.
   Samples/broker          Sample broker filter scripts.
   Deprecated/triggers     Deprecated triggers.

To Do in a subsequent change: Make corresponding doc changes.
//guest/perforce_software/sdp/dev/Server/Unix/p4/common/bin/triggers/CheckJobEditTrigger.py
#12 25269 Robert Cowham Clarify error message to mention JIRA
#11 24158 Robert Cowham Refactor - tidy up triggers to make a little more consistent.
No functional change.
#10 23153 Robert Cowham Make executable
#9 23152 Robert Cowham Refactor to push common arg_parse into P4Triggers
#8 22857 Robert Cowham Implement prefix for job names via parameter.
#7 22854 Robert Cowham Addressed pylint suggestions.
#6 22852 Robert Cowham Refactor reporting
#5 22851 Robert Cowham Works with job renaming now.
Trigger needs to be form-in not form-save.
#4 22850 Robert Cowham Refactored to move common stuff into P4Triggers.py
#3 22848 Robert Cowham Refactor for de-duplicating argparsing and doc strings.
Tidied up other code.
#2 22847 Robert Cowham Fixed problems with trigger table.
Save before tidying up.
#1 22833 Robert Cowham Initial version