Scripting Perforce: Triggers and Daemons

There are two primary methods of scripting Perforce:

  • Perforce triggers are user-written programs or scripts that are called by a Perforce server whenever certain operations (such as changelist submissions, changes to forms, attempts by users to log in or change passwords) are performed. If the script returns a value of 0, the operation continues; if the script returns any other value, the operation fails.

  • Daemons run at predetermined times, looking for changes to the Perforce metadata. When a daemon determines that the state of the depot has changed in some specified way, it runs other commands. For example, a daemon might look for newly submitted changelists and send email to users interested in tracking changes to those files. Perforce provides a number of tools that make writing daemons easier.

Triggers

Triggers allow you to extend or customize Perforce functionality. Consider the following common uses:

  • To validate changelist contents beyond the mechanisms afforded by the Perforce protections table. For example, you can use a pre-submit trigger to ensure that whenever file1 is submitted in a changelist, file2 is also submitted.

  • To perform some action before or after the execution of a particular Perforce command.

  • To validate forms, or to provide customized versions of Perforce forms. For example, you can use form triggers to generate a customized default workspace view when users run the p4 client command, or to ensure that users always enter a meaningful workspace description.

  • To configure Perforce to work with external authentication mechanisms such as LDAP or Active Directory.

    You might prefer to enable LDAP authentication by using an LDAP specification. For more information, see section "Authentication options" in chapter "Administering Perforce:Superuser Tasks."

  • To retrieve content from data sources archived outside of the Perforce repository.

For simplicity's sake, this guide refers to trigger scripts and programs as triggers.

To create a trigger and have Perforce execute it, you must do the following:

  1. Write the program or script. Triggers can be written in a shell script such as Perl, Python, or Ruby; or they can be written in any programming language that can interface with Perforce, including UNIX shell and compiled languages like C/C+.

    Triggers have access to trigger variables that can be used to get server state information, execution context, client information, information about the parameters passed to the trigger, and so on. For information about trigger variables, see Trigger script variables.

    Triggers communicate with the server using trigger variables or by using a dictionary of key/value pairs accessed via STDIN and STDOUT. For more information on these methods, see Communication between a trigger and the server.

    Triggers can also use the command-line client (p4.exe) or the Perforce scripting API's (P4-Ruby, P4-Python, P4-PHP) when data is needed that cannot be accessed by trigger variables. For more information, see Perforce APIs for Scripting.

    Triggers can be located on the server's file system or in the depot itself, for information on using a trigger that is located in the depot, see Executing triggers from the depot.

    Triggers can be written for portability across servers. For more information, see Writing triggers to support multiple Perforce servers.

  2. Use the p4 triggers command to create a trigger definition that determines when the trigger will fire. Trigger definitions are composed of four fields: these specify the trigger name, the event type that must occur, the location of the trigger and, in some cases, some file pattern that must be matched in order to fire the trigger.

    For more information, see Trigger definitions.

Warning

When you use trigger scripts, remember that Perforce commands that write data to the depot are dangerous and should be avoided. In particular, do not run the p4 submit command from within a trigger script.

Sample trigger

The following code sample is a bash auth-check type trigger that tries to authenticate a user (passed to the script using the %user% variable) using the Active Directory. If that fails, all users have the same "secret" password, and special user bruno is able to authenticate without a password.

USERNAME=$1
echo "USERNAME is $USERNAME"

# read user-supplied password from stdin
read USERPASS
echo Trying AD authentication for $USERNAME
echo $USERPASS | /home/perforce/p4auth_ad 192.168.100.80 389 DC=ad,DC=foo,DC=com $USERNAME
if [ $? == 0 ]
then
     # Successful AD
     echo Active Directory login successful
     exit 0
fi
# Compare user-supplied password with correct password, "secret"
PASSWORD=secret
if [ "$USERPASS" = $PASSWORD ]
then
     # Success
    exit 0
fi
if [ "$USERNAME" = "bruno" ]
then
    # Always let user bruno in
    exit 0
fi
# Failure
# password $USERPASS for $USERNAME is incorrect;
exit 1

To define this trigger, use the p4 triggers command, and add a line like the following to the triggers form:

bypassad auth-check auth "/home/perforce/bypassad.sh %user%"

The auth-check trigger is fired, if it exists, after a user executes the p4 login command. For authentication triggers, the password is sent on STDIN.

Trigger basics

This section contains essential information for writing triggers and trigger definitions. Detailed information about implementing each type of trigger is found in the sections that follow. The information in this section applies to all types of triggers.

For information about debugging triggers, see http://answers.perforce.com/articles/KB_Article/Debugging-Triggers/?q=debugging+triggers

Trigger definitions

After you have written a trigger, you create the trigger definition by issuing the p4 triggers command and providing trigger information in the triggers form. You must be a Perforce superuser to run this command. The p4 triggers form looks like this:

Triggers:
  relnotecheck change-submit //depot/bld/...  "/usr/bin/rcheck.pl %user%"
  verify_jobs  change-submit //depot/...      "/usr/bin/job.py %change%"

As with all Perforce commands that use forms, field names (such as Triggers:) must be flush left (not indented) and must end with a colon, and field values (that is, the set of lines you add, one for each trigger) must be indented with spaces or tabs on the lines beneath the field name.

Each line in the trigger form you fill out when you use the p4 Triggers command has four fields. These are briefly described in the following table. Values for three of these fields vary with the trigger type; these values are described in additional detail in the sections describing each type of trigger. The name field uses the same format for all trigger types.

Field

Meaning

name

The user-defined name of the trigger.

To use the same trigger script with multiple file patterns, list the same trigger multiple times on contiguous lines in the trigger table. Use exclusionary mappings to prevent files from activating the trigger script; the order of the trigger entries matters, just as it does when exclusionary mappings are used in views. In this case, only the command of the first such trigger line is used.

type

Triggers are divided into eight categories: submit triggers, command triggers, shelve triggers, edge-server triggers, fix triggers, form triggers, authentication triggers, and archive triggers. One or more types is defined for each of these categories. For example, submit triggers include the change-submit, change-content, change-commit, and change-failed types.

Please consult the section describing the category of interest to determine which types relate to that trigger.

path

The use of this field varies with the trigger type. For example, for submit, edge server, and shelve triggers, this field is a file pattern in depot syntax. When a user submits a changelist that contains files that match this pattern, the trigger script executes.

Please consult the section describing the trigger of interest to determine which path is appropriate for that trigger.

command

The trigger for the Perforce server to run when the conditions implied by the trigger definition is satisfied.

You must specify the name of the trigger script or executable in ASCII, even when the server is running in Unicode mode and passes arguments to the trigger script in UTF8.

Specify the trigger in a way that allows the Perforce server to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

On those platforms where the operating system does not know how to run the trigger, you will need to specify an interpreter in the command field. For example, Windows does not know how to run .pl files.

lo form-out label  "perl //myscripts/validate.pl"

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl //depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

Triggers are run in the order listed in the trigger table; if a trigger script fails for a specified type, subsequent trigger scripts also associated with that type are not run.

The p4 triggers command has a very simple syntax:

p4 triggers [ -i | -o ]

  • With no flags, the user's editor is invoked to specify the trigger definitions.

  • The -i flag reads the trigger table from standard input.

  • The -o flag displays all the trigger definitions stored in the trigger table.

Execution environment

When testing and debugging triggers, remember that any p4 commands invoked from within the script will run within a different environment (P4USER, P4CLIENT, and so on) than that of the calling user. You must therefore take care to initialize the environment you need from within the trigger script and not inherit these values from the current environment. For example:

export P4USER=george
export P4PASSWD=abracadabra
cd /home/pforce/database

p4 admin checkpoint
ls -l checkpoint.* journal*

In general, it is good practice to observe the following guidelines:

  • Wherever possible, use the full path to executables.

  • For path names that contain spaces, use the short path name.

    For example, C:\Program Files\Perforce\p4.exe is most likely located in C:\PROGRA~1\Perforce\p4.exe.

  • Login tickets may not be located in the same place as they were during testing; for testing, you can pass in data with p4 login < input.txt.

  • For troubleshooting, log output to a file. For example:

    date /t >> trigger.log
    p4 info >> trigger.log
    C:\PROGRA~1\Perforce\p4.exe -p myServer:1666 info
    

    If the trigger.log isn't updated at all, the trigger was not fired. If the first p4 info fails, and the second p4 info runs, you'll know whether there were differences in environmental settings.

  • Perforce commands in trigger scripts are always run by a specific Perforce user. If no user is specified, an extra Perforce license for a user named SYSTEM (or on UNIX, the user that owns the p4d process) is assumed. To prevent this from happening:

    • Pass a %user% argument to the trigger that calls each Perforce command to ensure that each command is called by that user. For example, if Joe submits a changelist that activates trigger script trigger.pl, and trigger.pl calls the p4 changes command, the script can run the command as p4 -u %user% changes.

    • Set P4USER for the account that runs the trigger to the name of an existing user. (If your Perforce server is installed as a service under Windows, note that Windows services cannot have a P4USER value; on Windows, you must therefore pass a user value to each command as described above.)

  • By default, the Perforce service runs under the Windows local System account. The System account may have different environmental configurations (including not just Perforce-related variables, but PATH settings and file permissions) than the one in which you are using to test or write your trigger.

  • Because Windows requires a real account name and password to access files on a network drive, if the trigger script resides on a network drive, you must configure the service to use a real userid and password to access the script.

  • On Windows, standard input does not default to binary mode. In text mode, line ending translations are performed on standard input, which is inappropriate for binary files.

    If you are using archive triggers against binary files on a Windows machine, you must prevent unwanted line-ending translations by ensuring that standard input is changed to binary mode (O_BINARY).

Executing triggers from the depot

You can execute a trigger that is located in the depot. To do this, in the command field of the trigger definition, specify the path name of a plain or stream depot file (with an optional revision number) that contains the trigger and enclose it in % signs. If you need to pass additional variables to the trigger, add them in the command field as you usually do. The server will create a temporary file that holds the contents of the file path name you have specified for the command field. (Working with a temporary file is preferable for security reasons and because depot files cannot generally be executed without some further processing.)

The depot file must already exist to be used as a trigger. All file types are acceptable so long as the content is available. For text types on unicode-enabled servers, the temporary file will be in UTF8. Protections on the depot script file must be such that only trusted users can see or write the content.

If the file path name contains spaces or if you need to pass additional parameters, you must enclose the command field in quotes.

In the next trigger definition, note that an intepreter is specified for the trigger. Specifying the interpreter is needed for those platforms where the operating system does not know how to run the trigger. For example, Windows does not know how to run .pl files.

lo form-out label  "perl %//admin/validate.pl%"

In the next trigger definition, the depot path is quoted because of the revision number. The absence of an interpreter value implies that the operating system knows how to run the script directly.

lo form-out branch "%//depot/scripts/validate#123.exe%"

Warning

A depot file path name may not contain reserved characters. This is because the hex replacement contains a percent sign, which is the terminator for a %var%. For example, no file named @myScript can be used because it would be processed as %40myScript inside a var %%40myScript%.

Communication between a trigger and the server

Triggers can communicate with the server in one of two ways: by using the variables described in Trigger script variables or by using a dictionary of key/value pairs accessed via STDIN and STDOUT. The setting of the triggers.io configuration variable determines which method is used. The method chosen determines the content of STDIN and STDOUT and also affects how trigger failure is handled. The following table summarizes the effect of these settings. Client refers to the client application (Swarm, P4V, P4Win, etc) that is connected to the server where the trigger executes.

 

triggers.io = 0

triggers.io = 1

Trigger succeeds

The trigger communicates with the server using trigger variables.

STDIN is only used by archive or authentication triggers. It is the file content for an archive trigger, and it is the password for an authentication trigger.

The trigger's STDOUT is sent as an unadorned message to the client for all triggers except archive triggers; for archive triggers, the command's standard output is the file content.

The trigger should exit with a zero value.

The trigger communicates with the server using STDIN and STDOUT.

STDIN is a textual dictionary of name-value pairs of all the trigger variables except for %clienthost% and %peerhost%.

This setting does not affect STDIN values for archive and authentication triggers.

The trigger should exit with a zero value.

Trigger fails

The trigger's STDOUT and STDERR are sent to the client as the text of a trigger failure error message.

The trigger should exit with a non-zero value.

STDOUT is a textual dictionary that contains error information. STDERR is merged with STDOUT.

Failure indicates that the trigger script can't be run, that the output dictionary includes a failure message, or that the output is misformatted. The execution error is logged by the server, and the server sends the client the information specified by STDOUT. If no dictionary is provided, the server sends the client a generic message that something has gone wrong.

The dictionary format is a sequence of lines containing key:value pairs. Any non-printable characters must be percent-encoded. Data is expected to be UTF8-encoded on unicode-enabled servers. Here are some examples of how the %client%, %clientprog%, %command%, and %user% variables would be represented in the %dictionary:

client:jgibson-aaaatchoooo
clientprog:P4/LINUX45X86_128/2017.9.MAIN/1773263782 (2017/OCT/09).
command:user-dwim
user:jgibson

The example above shows only a part of the dictionary. When variables are passed in this way, all the variables described in Trigger script variables are passed in STDIN, and the trigger script must read all of STDIN even if the script only references some of these variables. If the script does not read all of STDIN, the script will fail and the server will see errors like this:

write: yourTriggerScript: Broken pipe

The trigger must send back a dictionary to the server via STDOUT. The dictionary must at a minimum contain an action with an optional message. The action is either pass or fail. Nonprintable characters must be percent encoded. For example:

action:fail
message:too bad!

Malformed trigger response dictionaries and execution problems are reported to the client with a generic error. A detailed message is recorded in the server log.

The introduction to this section suggested that the two ways of communicating with the server were mutually exclusive. In general, they are. There is one case, however, in which you must specify variables on the command line even if you set triggers.io to 1. This is when you want to reference the %peerhost% or %clienthost% variables. These variables are very expensive to pass. For their values to be included in the dictionary, you must specify one or both on the command line.

The following is a sample Perl program that echoes its input dictionary to the user:

use strict;
use warnings FATAL=>"all';
use open qw/ :std :utf8 /;
use Data::Dumper;
use URI::Escape;

$Data::Dumper::Quotekeys = 0;
$Data::Dumper::Sortkeys  = 1;

my %keys = map { /(.*):(.*)/ } <STDIN>;

print "action:pass\nmessage:" . uri_escape Dumper \ %keys;

The listing begins with some code that sets Perl up for basic Unicode suppport and adds some error handling. The gist of the program is in line 8. <STDIN> is a file handle that is applied to the map{}, where the map takes one line of input at a time and runs the function between the map's {}. The expression (.*):(.*) is a regular expression with a pair of capture groups that are split by the colon. No key the server sends has a colon in it, so the first .* will not match. Since most non-printable characters (like newline) are percent-encoded in the dictionary, a trigger can expect every key/value pair to be a single line; hence the single regular expression can extract both the key and the value. The return values of the regular expression are treated as the return vaues for the map's function, which is a list of strings. When a list is assigned to a hash, Perl tries to make it into a list of key/value pairs. Because we know it's an even list, this works and we've gotten our data. The print command makes the result dictionary and sends it to the server. Calling it a pass action tells the server to let the command continue and that the message to send the user is the formated hash of the trigger's input dictionary.

Exceptions

Setting triggers.io to 1 does not affect authentication and archive triggers; these behave as if triggers.io were set to 0 no matter what the actual setting is.

Compatibility with old triggers

When you set the triggers.io variable to 1, it affects how the server runs all scripts, both old and new. If you don't want to rewrite your old trigger scripts, you can insert a shim between the trigger table and the old trigger script, which collects trigger output and formats it as the server now expects it. That is, the shim runs the old trigger, captures its output and return code, and then emits the appropriate dictionary back to the server. The following Perl script illustrates such a shim:

t form-out label unset "perl shim.pl original_trigger.exe orig_args..."

The shim.pl program might look like this:

use strict;
use warnings FATAL => 'all';
use open qw/ :std :utf8 /;
use URI::Escape;
use IPC::Run3;

@_=<STDIN>;
run3 \@ARGV, undef, \$_, \$_;
print 'action:' . (? ? 'fail' : 'pass' ) . "\nmessage:" . uri_escape $_;

Using multiple triggers

Submit and form triggers are run in the order in which they appear in the triggers table. If you have multiple triggers of the same type that fire on the same path, each is run in the order in which it appears in the triggers table. If one of these triggers fails, no further triggers are executed.

Example 10. Multiple triggers on the same file

All *.c files must pass through the scripts check1.sh, check2.sh, and check3.sh:

Triggers:
  check1 change-submit //depot/src/*.c "/usr/bin/check1.sh %change%"
  check2 change-submit //depot/src/*.c "/usr/bin/check2.sh %change%"
  check3 change-submit //depot/src/*.c "/usr/bin/check3.sh %change%"

If any trigger fails (for instance, check1.sh), the submit fails immediately, and none of the subsequent triggers (that is, check2.sh and check3.sh) are called. Each time a trigger succeeds, the next matching trigger is run.


To link multiple file specifications to the same trigger (and trigger type), list the trigger multiple times in the trigger table.

Example 11. Activating the same trigger for multiple filespecs

Triggers:
  bugcheck change-submit //depot/*.c   "/usr/bin/check4.sh %change%"
  bugcheck change-submit //depot/*.h   "/usr/bin/check4.sh %change%"
  bugcheck change-submit //depot/*.cpp "/usr/bin/check4.sh %change%"

In this case, the bugcheck trigger runs on the *.c files, the *.h files, and the *.cpp files.


Multiple submit triggers of different types that fire on the same path fire in the following order:

  1. change-submit (fired on changelist submission, before file transmission)

  2. change-content triggers (after changelist submission and file transmission)

  3. change-commit triggers (on any automatic changelist renumbering by the server)

Similarly, form triggers of different types are fired in the following order:

  1. form-out (form generation)

  2. form-in (changed form is transmitted to the server)

  3. form-save (validated form is ready for storage in the Perforce database)

  4. form-delete (validated form is already stored in the Perforce database)

Writing triggers to support multiple Perforce servers

To call the same trigger script from more than one Perforce server, use the %serverhost%, %serverip%, and %serverport% variables to make your trigger script more portable.

For instance, if you have a script that uses hardcoded port numbers and addresses...

#!/bin/sh
# Usage: jobcheck.sh changelist
CHANGE=$1
P4CMD="/usr/local/bin/p4 -p 192.168.0.12:1666"
$P4CMD describe -s $1 | grep "Jobs fixed...\n\n\t" > /dev/null

...and you call it with the following line in the trigger table...

jc1 change-submit //depot/qa/... "jobcheck.sh %change%"

...you can improve portability by changing the script as follows...

#!/bin/sh
# Usage: jobcheck.sh changelist server:port
CHANGE=$1
P4PORT=$2
P4CMD="/usr/local/bin/p4 -p $P4PORT"
$P4CMD describe -s $1 | grep "Jobs fixed...\n\n\t" > /dev/null

...and passing the server-specific data as an argument to the trigger script:

jc2 change-submit //depot/qa/... "jobcheck.sh %change% %serverport%"

Note that the %serverport% variable can contain a transport prefix: ssl, tcp6, or ssl6.

For a complete list of variables that apply for each trigger type, see Trigger script variables.

Triggering on submits

To configure Perforce to run trigger scripts when users submit changelists, use submit triggers: these are triggers of type change-submit, change-content, and change-commit. You can also use change-failed triggers for the p4 submit or the p4 populate command.

The following table describes the fields of a submit trigger. For sample definitions, see the subsequent sections, describing each trigger subtype.

Field

Meaning

type

  • change-submit: Execute a submit trigger after changelist creation, but before file transfer. Trigger may not access file contents.

  • change-content: Execute a submit trigger after changelist creation and file transfer, but before file commit.

    To obtain file contents, use the revision specifier @=change (where change is the changelist number of the pending changelist as passed to the script in the %changelist% variable) with commands such as p4 diff2, p4 files, p4 fstat, and p4 print.

  • change-commit: Execute a submit trigger after changelist creation, file transfer, and changelist commit.

  • change-failed: Execute a submit trigger if the p4 submit or the p4 populate command fails. This trigger only fires on errors that occurr after a commit process has started. It does not fire for early usage errors, or due to errors from the submit form. That is, if an edge or change trigger could have run, then the change-failed trigger will fire if that commit fails.

path

A file pattern in depot syntax.

When a user submits a changelist that contains any files that match this file pattern, the trigger specified in the command field is run. Use exclusionary mappings to prevent triggers from running on specified files.

command

The trigger for the Perforce server to run when a user submits a changelist that contains any file patterns specified by path. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl //depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

For change-submit and change-content triggers (and their corresponding edge server triggers), changelist submission does not continue if the trigger fails. For change-commit triggers, changelist submission succeeds regardless of trigger success or failure, but subsequent change-commit triggers do not fire if the script fails.

Even when a change-submit or change-content trigger script succeeds, the submit can fail because of subsequent trigger failures, or for other reasons. Use change-submit and change-content triggers only for validation, and use change-commit triggers or daemons for operations that are contingent on the successful completion of the submit.

Be aware of edge cases: for example, if a client workspace has the revertunchanged option set, and a user runs p4 submit on a changelist with no changed files, a changelist has been submitted with files contents, but no changes are actually committed. (That is, a change-submit trigger fires, a change-content trigger fires, but a change-commit trigger does not.)

Change-submit triggers

Use the change-submit trigger type to create triggers that fire after changelist creation, but before files are transferred to the server. Because change-submit triggers fire before files are transferred to the server, these triggers cannot access file contents. Change-submit triggers are useful for integration with reporting tools or systems that do not require access to file contents.

In addition to the p4 submit command, the p4 populate command, which does an implicit submit as part of its branching action, fires a change-submit trigger to allow for validation before submission.

Example 12. The following change-submit trigger is an MS-DOS batch file that rejects a changelist if the submitter has not assigned a job to the changelist. This trigger fires only on changelist submission attempts that affect at least one file in the //depot/qa branch.

@echo off

rem REMINDERS
rem - If necessary, set Perforce environment vars or use config file
rem - Set PATH or use full paths (C:\PROGRA~1\Perforce\p4.exe)
rem - Use short pathnames for paths with spaces, or quotes
rem - For troubleshooting, log output to file, for instance:
rem - C:\PROGRA~1\Perforce\p4 info >> trigger.log

if not x%1==x goto doit
echo Usage is %0[change#]

:doit
p4 describe -s %1|findstr "Jobs fixed...\n\n\t" > nul
if errorlevel 1 echo No jobs found for changelist %1
p4 describe -s %1|findstr "Jobs fixed...\n\n\t" > nul

To use the trigger, add the following line to your triggers table:

sample1   change-submit //depot/qa/...   "jobcheck.bat %changelist%"

Every time a changelist is submitted that affects any files under //depot/qa, the jobcheck.bat file is called. If the string "Jobs fixed..." (followed by two newlines and a tab character) is detected, the script assumes that a job has been attached to the changelist and permits changelist submission to continue. Otherwise, the submit is rejected.

The second findstr command ensures that the final error level of the trigger script is the same as the error level that determines whether to output the error message.


Change-content triggers

Use the change-content trigger type to create triggers that fire after changelist creation and file transfer, but prior to committing the submit to the database. Change-content triggers can access file contents by using the p4 diff2, p4 files, p4 fstat, and p4 print commands with the @=change revision specifier, where change is the number of the pending changelist as passed to the trigger script in the %changelist% variable.

Use change-content triggers to validate file contents as part of changelist submission and to abort changelist submission if the validation fails.

Even when a change-submit or change-content trigger script succeeds, the submit can fail because of subsequent trigger failures, or for other reasons. Use change-submit and change-content triggers only for validation, and use change-commit triggers or daemons for operations that are contingent on the successful completion of the submit.

Example 13. The following change-content trigger is a Bourne shell script that ensures that every file in every changelist contains a copyright notice for the current year.

The script assumes the existence of a client workspace called copychecker that includes all of //depot/src. This workspace does not have to be synced.

#!/bin/sh
# Set target string, files to search, location of p4 executable...
TARGET="Copyright 'date +%Y' Example Company"
DEPOT_PATH="//depot/src/..."
CHANGE=$1
P4CMD="/usr/local/bin/p4 -p 1666 -c copychecker"
XIT=0
echo ""
# For each file, strip off #version and other non-filename info
# Use sed to swap spaces w/"%" to obtain single arguments for "for"
for FILE in '$P4CMD files $DEPOT_PATH@=$CHANGE | \
  sed -e 's/\(.*\)\#[0-9]* - .*$/\1/' -e 's/ /%/g''
do
  # Undo the replacement to obtain filename...
  FILE="'echo $FILE | sed -e 's/%/ /g''"
# ...and use @= specifier to access file contents:
  # p4 print -q //depot/src/file.c@=12345
  if $P4CMD print -q "$FILE@=$CHANGE" | grep "$TARGET" > /dev/null
  then echo ""
  else
      echo "Submit fails: '$TARGET' not found in $FILE"
      XIT=1
  fi
done
exit $XIT

To use the trigger, add the following line to your triggers table:

sample2  change-content //depot/src/... "copydate.sh %change%"

The trigger fires when any changelist with at least one file in //depot/src is submitted. The corresponding DEPOT_PATH defined in the script ensures that of all the files in the triggering changelist, only those files actually under //depot/src are checked.


Change-commit triggers

Use the change-commit trigger type to create triggers that fire after changelist creation, file transfer, and changelist commission to the database. Use change-commit triggers for processes that assume (or require) the successful submission of a changelist.

Example 14. A change-commit trigger that sends emails to other users who have files open in the submitted changelist.

#!/bin/sh
# mailopens.sh - Notify users when open files are updated
changelist=$1
workspace=$2
user=$3
p4 fstat @$changelist,@$changelist | while read line
do
  # Parse out the name/value pair.
  name='echo $line | sed 's/[\. ]\+\([^ ]\+\) .\+/\1/''
  value='echo $line | sed 's/[\. ]\+[^ ]\+ \(.\+\)/\1/''
  if [ "$name" = "depotFile" ]
  then
    # Line is "... depotFile <depotFile>". Parse to get depotFile.
    depotfile=$value
  elif [ "'echo $name | cut -b-9'" = "otherOpen" -a \
    "$name" != "otherOpen" ]
  then
    # Line is "... ... otherOpen[0-9]+ <otherUser@otherWorkspace>".
    # Parse to get otherUser and otherWorkspace.
    otheruser='echo $value | sed 's/\(.\+\)@.\+/\1/''
    otherworkspace='echo $value | sed 's/.\+@\(.\+\)/\1/''
    # Get email address of the other user from p4 user -o.
    othermail='p4 user -o $otheruser | grep Email: \
      | grep -v \# | cut -b8-'

    # Mail other user that a file they have open has been updated
    mail -s "$depotfile was just submitted" $othermail <<EOM
The Perforce file: $depotfile
was just submitted in changelist $changelist by Perforce user $user
from the $workspace workspace.  You have been sent this message
because you have this file open in the $otherworkspace workspace.
EOM
  fi
done
exit 0

To use the trigger, add the following line to your triggers table:

sample3  change-commit //... "mailopens.sh %change% %client% %user%"

Whenever a user submits a changelist, any users with open files affected by that changelist receive an email notification.


Triggering before or after commands

Triggers of type command allow you to configure Perforce to run a trigger before or after a given command executes. Generally, you might want to execute a script before a command runs to prevent that command from running; you might want to run a script after a command if you want to connect its action with that of another tool or process.

The following table describes the fields of the command trigger.

Field

Meaning

type

command

The command to execute is specified in the path field.

path

The pre-user-command value specifies the command before which the trigger should execute. The post-user-command value specifies the command after which the trigger should execute. command can be a regular expression. For additional information about the grammar of regular expressions, see p4 help grep.

Here are examples of possible path values:

pre-user-login           \\ before the login command
post-user-(add|edit)     \\ after the add or edit command
pre-user-obliterate      \\ before the obliterate command
(pre|post)-user-sync     \\ before or after the sync command

If you want to match a command name that's a substring of another valid command, you should use the end-of-line metacharacter to terminate matching. For example, use change$ so you don't also match changes

You cannot create a pre-user-info trigger.

command

The trigger for the Perforce server to run when the condition implied by path is satisfied.

Specify the command in a way that allows the Perforce server to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variable.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl //depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

One thing you might need to do in a command trigger is to parse the input dictionary. The following code sample does just that, putting the key/value store in a Perl data structure ready for access, and it shows how to send data back to the server.

use strict
use warnings FATAL => 'all';
use open qw / :std :utf8 /p;
use Data:Dumper;
use URI::Escape;

$Data:Dumper::Quotekeys = 0;
$Data:Dumper::Sortkeys = 1;

my %keys = map { /(.*):(.*)/ } <STDIN>;

print "action:pass\nmessage:"  . uri_escape Dumper \ %keys;

The listing is a bit bigger than it needs to be in order to illustrate good trigger coding practice: it begins with some code that sets Pearl up for basic Unicode suppport and adds some error handling. The gist of the program is in line 8. <STDIN> is a file handle that is applied to the map{}, where the map takes one line of input at a time and runs the function between the map's {}. The expression (.*):(.*) is a regular expression with a pair of capture groups that are split by the colon. No key the server sends has a colon in it, so the first .* will not match. Since most non-printable characters (like newline) are percent-encoded in the dictionary, a trigger can expect every key/value pair to be a single line; hence the single regular expression can extract both the key and the value. The return values of the regular expression are treated as the return vaues for the map's function, which is a list of strings. When a list is assigned to a hash, Perl tries to make it into a list of key/value pairs. Because we know it's an even list, this works and we've gotten our data.

The print command makes the result dictionary and sends it to the server. Calling it a pass action tells the server to let the command continue and that the message to send the user is the formated hash of the trigger's input dictionary.

After you write the script, you can add it to the trigger table by editing the p4 triggers form.

Triggers:
  myTrig command  post-user-move "perl /usr/bin/test.pl "

After the p4 move command executes, this trigger fires.

Triggering on shelving events

To configure Perforce to run trigger scripts when users work with shelved files, use shelve triggers: these are triggers of type shelve-submit, shelve-commit, and shelve-delete.

The following table describes the fields of a shelving type trigger:

Field

Meaning

type

  • shelve-submit: Execute a pre-shelve trigger after changelist has been created and files locked, but prior to file transfer.

  • shelve-commit: Execute a post-shelve trigger after files are shelved.

  • shelve-delete: Execute a shelve trigger prior to discarding shelved files.

path

A file pattern in depot syntax.

If a shelve contains any files in the specified path, the trigger fires. To prevent some shelving operations from firing these triggers, use an exclusionary mapping in the path.

command

The trigger for the Perforce server to run when a matching path applies for the trigger type. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments anything that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

Shelve-submit triggers

The shelve-submit trigger works like the change-submit trigger; it fires after the shelved changelist is created, but before before files are transferred to the server. Shelve-submit triggers are useful for integration with reporting tools or systems that do not require access to file contents.

Example 15. A site administrator wants to prohibit the shelving of large disk images; the following shelve-submit trigger rejects a shelving operation if the changelist contains .iso files.

#!/bin/sh

# shelve1.sh - Disallow shelving of certain file types

# This trigger always fails: when used as a shelve-submit trigger
# with a specified path field, guarantees that files matching that
# path are not shelved

echo "shelve1.sh: Shelving operation disabled by trigger script."

exit 1

To use the trigger, add the following line to your triggers table, specifying the path for which shelving is to be prohibited in the appropriate field, for example:

shelving1   shelve-submit   //....iso   shelve1.sh

Every time a changelist is submitted that affects any .iso files in the depot, the noshelve.sh script runs, and rejects the request to shelve the disk image files.


Shelve-commit triggers

Use the shelve-commit trigger to create triggers that fire after shelving and file transfer. Use shelve-commit triggers for processes that assume (or require) the successful submission of a shelving operation.

Example 16. A shelve-commit trigger that notifies a user (in this case, reviewers) about a shelved changelist.

#!/bin/sh
# shelve2.sh - Send email to reviewers when open files are shelved
changelist=$1
workspace=$2
user=$3

mail -s "shelve2.sh: Files available for review" reviewers << EOM
   $user has created shelf from $workspace in $changelist"
EOM

exit 0

To use the trigger, add the following line to your triggers table:

shelving2  shelve-commit //... "shelve2.sh %change% %client% %user%"

Whenever a user shelves a changelist, reviewers receive an email notification.


Shelve-delete triggers

Use the shelve-delete trigger to create triggers that fire after users discard shelved files.

Example 17. A shelve-delete trigger that notifies reviewers that shelved files have been abandoned.

#!/bin/sh
# shelve3.sh - Send email to reviewers when files deleted from shelf
changelist=$1
workspace=$2
user=$3

mail -s "shelve3.sh: Shelf $changelist deleted" reviewers << EOM
   $user has deleted shelved changelist $changelist"
EOM

exit 0

To use the trigger, add the following line to your triggers table:

shelving3  shelve-delete //... "shelve3.sh %change% %client% %user%"

Whenever a user deletes files from the shelf, reviewers receive an email notification. A more realistic example might check an external (or internal) data source to verify that code review was complete complete before permitting the user to delete the shelved files.


Triggering on fixes

To configure Perforce to run trigger scripts when users add or delete fixes from changelists, use fix triggers: these are triggers of type fix-add and fix-delete.

The special variable %jobs% is available for expansion with fix triggers; it expands to one argument for every job listed on the p4 fix command line (or in the Jobs: field of a p4 change or p4 submit form), and must therefore be the last argument supplied to the trigger script.

The following table describes the fields used for a fix trigger definition.

Field

Meaning

type

  • fix-add: Execute fix trigger prior to adding a fix.

  • fix-delete: Execute fix trigger prior to deleting a fix.

path

Use fix as the path value.

command

The trigger for the Perforce server to run when a user adds or deletes a fix. Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

For fix-add and fix-delete triggers, fix addition or deletion continues whether the script succeeds or fails.

Fix-add and fix-delete triggers

Example 18. The following script, when copied to fixadd.sh and fixdel.sh, fires when users attempt to add or remove fix records, whether by using the p4 fix command, or by modifying the Jobs: field of the forms presented by the p4 change and p4 submit commands.

#!/bin/bash
# fixadd.sh, fixdel.sh - illustrate fix-add and fix-delete triggers

COMMAND=$0
CHANGE=$1
NUMJOBS=$(($# - 1 ))

echo $COMMAND: fired against $CHANGE with $NUMJOBS job arguments.
echo $COMMAND: Arguments were: $*

These fix-add and fix-delete triggers fire whenever users attempt to add (or delete) fix records from changelists. To use the trigger, add the following lines to the trigger table:

sample4   fix-add    fix "fixadd.sh %change% %jobs%"
sample5   fix-delete fix "fixdel.sh %change% %jobs%"

Using both copies of the script, observe that fixadd.sh is triggered by p4 fix, the fixdel.sh script is triggered by p4 fix -d, and either script may be triggered by manually adding (or deleting) job numbers from within the Jobs: field in a changelist form - either by means of p4 change or as part of the p4 submit process.

Because the %jobs% variable is expanded to one argument for every job listed on the p4 fix command line (or in the Jobs: field of a p4 change or p4 submit form), it must be the last argument supplied to any fix-add or fix-delete trigger script.


Triggering on forms

To configure Perforce to run trigger scripts when users edit forms, use form triggers: these are triggers of type form-save, form-in, form-out, form-delete, and form-commit.

Use form triggers to generate customized field values for users, to validate data provided on forms, to notify other users of attempted changes to form data, and to otherwise interact with process control and management tools.

If you write a trigger that fires on trigger forms, and the trigger fails in such a way that the p4 triggers command no longer works, the only recourse is to remove the db.triggers file in the server root directory.

The following table describes the fields of a form trigger definition:

Field

Meaning

type

  • form-save: Execute a form trigger after the form contents are parsed, but before the contents are stored in the Perforce database. The trigger cannot modify the form specified in %formfile% variable.

  • form-out: Execute form trigger upon generation of form to end user. The trigger can modify the form.

  • form-in: Execute form trigger on edited form before contents are parsed and validated by the Perforce server. The trigger can modify the form.

  • form-delete: Execute form trigger after the form contents are parsed, but before the form is deleted from the Perforce database. The trigger cannot modify the form.

  • form-commit: Execute form trigger after the form has been committed for access to automatically-generated fields such as jobname, dates, etc.

path

The name of the type of form, (branch, change, client, depot, group, job, label, protect, server, spec, stream, triggers, typemap, or user).

command

The trigger for the Perforce server to run when the type of form specified in the path field is processed.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

For form-in, form-out, form-save, and form-delete triggers, the data in the specification becomes part of the Perforce database if the script succeeds. Otherwise, the database is not updated.

Warning

Client workspace specifications whose Name: field begins with p4sandbox- must not be altered in any way (either the name or contents), or P4Sandbox will be unable to work properly with the shared repository.

When creating and managing form triggers:

  • Do not create new form trigger scripts that can modify P4Sandbox client specifications.

  • If you have existing form trigger scripts that are capable of modifying P4Sandbox client specifications, you must change the scripts to prevent this behavior. For example, you maintain a script that examines the %client% variable as passed to the script. If the Name: field of the user's client workspace specification begins with p4sandbox-, the trigger must perform one of the following behaviors:

  • Fail unconditionally for any p4sandbox- workspace. This will prevent any of your users from using P4Sandbox with your server.

  • Succeed without modifying the client workspace spec in any way; all users in your organization can use your server as a P4Sandbox remote depot.

For more information, see "Administering P4Sandbox" in the Perforce Sandbox User's Guide.

Form-save triggers

Use the form-save trigger type to create triggers that fire when users send changed forms to the server. Form-save triggers are called after the form has been parsed by the server but before the changed form is stored in the Perforce metadata.

Example 19. To prohibit certain users from modifying their client workspaces, add the users to a group called lockedws and use the following form-save trigger.

This trigger denies attempts to change client workspace specifications for users in the lockedws group, outputs an error message containing the user name, IP address of the user's workstation, and the name of the workspace on which a modification was attempted, and notifies an administrator.

#!/bin/bash
NOAUTH=lockedws
USERNAME=$1
WSNAME=$2
IPADDR=$3

GROUPS='p4 groups "$1"'

if echo "$GROUPS" | grep -qs $NOAUTH
then
   echo "$USERNAME ($IPADDR) in $NOAUTH may not change $WSNAME"
   mail -s "User $1 workspace mod denial" [email protected]
   exit 1
else
   exit 0
fi

This form-save trigger fires on client forms only. To use the trigger, add the following line to the trigger table:

sample6   form-save  client  "ws_lock.sh %user% %client% %clientip%"

Users whose names appear in the output of p4 groups lockedws have changes to their client workspaces parsed by the server, and even if those changes are syntactically correct, the attempted change to the workspace is denied, and an administrator is notified of the attempt.


Form-out triggers

Use the form-out trigger type to create triggers that fire whenever the Perforce server generates a form for display to the user.

Warning

Never use a Perforce command in a form-out trigger that fires the same form-out trigger, or infinite recursion will result. For example, never run p4 job -o from within a form-out trigger script that fires on job forms.

Example 20. The default Perforce client workspace view maps the entire depot //depot/... to the user's client workspace. To prevent novice users from attempting to sync the entire depot, this Perl script changes a default workspace view of //depot/... in the p4 client form to map only the current release codeline of //depot/releases/main/...

#!/usr/bin/perl
# default_ws.pl - Customize the default client workspace view.
$p4 = "p4 -p localhost:1666";
$formname = $ARGV[0];  # from %formname% in trigger table
$formfile = $ARGV[1];  # from %formfile% in trigger table
# Default server-generated workspace view and modified view
# (Note: this script assumes that //depot is the only depot defined)
$defaultin = "\t//depot/... //$formname/...\n";
$defaultout = "\t//depot/releases/main/... //$formname/...\n";
# Check "p4 clients": if workspace exists, exit w/o changing view.
# (This example is inefficient if there are millions of workspaces)
open CLIENTS, "$p4 clients |" or die "Couldn't get workspace list";
while ( <CLIENTS> )
{
        if ( /^Client $formname .*/ ) { exit 0; }
}
# Build a modified workspace spec based on contents of %formfile%
$modifiedform = "";
open FORM, $formfile or die "Trigger couldn't read form tempfile";
while ( <FORM> )
{       ## Do the substitution as appropriate.
        if ( m:$defaultin: ) { $_ = "$defaultout"; }
        $modifiedform .= $_;
}
# Write the modified spec back to the %formfile%,
open MODFORM, ">$formfile" or die "Couldn't write form tempfile";
print MODFORM $modifiedform;
exit 0;

This form-out trigger fires on client workspace forms only. To use the trigger, add the following line to the trigger table:

sample7   form-out  client  "default_ws.pl %formname% %formfile%"

New users creating client workspaces are presented with your customized default view.


Form-in triggers

Use the form-in trigger type to create triggers that fire when a user attempts to send a form to the server, but before the form is parsed by the Perforce server.

Example 21. All users permitted to edit jobs have been placed in a designated group called jobbers. The following Python script runs p4 group -o jobbers with the -G (Python marshaled objects) flag to determine if the user who triggered the script is in the jobbers group.

import sys, os, marshal

# Configure for your environment
tuser = "triggerman"   # trigger username
job_group = "jobbers"  # Perforce group of users who may edit jobs

# Get trigger input args
user = sys.argv[1]

# Get user list
# Use global -G flag to get output as marshaled Python dictionary
CMD = "p4 -G -u %s -p 1666 group -o %s" % \
        (tuser, job_group)
result = {}
result = marshal.load(os.popen(CMD, 'r'))

job_users = []
for k in result.keys():
        if k[:4] == 'User': # user key format: User0, User1, ...
                u = result[k]
                job_users.append(u)

# Compare current user to job-editing users.
if not user in job_users:
        print "\n\t>>> You don't have permission to edit jobs."
        print "\n\t>>> You must be a member of '%s'.\n" % job_group
        sys.exit(1)
else: # user is in job_group -- OK to create/edit jobs
        sys.exit(0)

This form-in trigger fires on job forms only. To use the trigger, add the following line to the trigger table:

sample8   form-in  job  "python jobgroup.py %user%"

If the user is in the jobbers group, the form-in trigger succeeds, and the changed job is passed to the Perforce server for parsing. Otherwise, an error message is displayed, and changes to the job are rejected.


Form-delete triggers

Use the form-delete trigger type to create triggers that fire when users attempt to delete a form, after the form is parsed by the Perforce server, but before the form is deleted from the Perforce database.

Example 22. An administrator wants to enforce a policy that users are not to delete jobs from the system, but must instead mark such jobs as closed.

#!/bin/sh

echo "Jobs may not be deleted. Please mark jobs as closed instead."
exit 1

This form-delete trigger fires on job forms only. To use the trigger, add the following line to the trigger table:

sample9   form-delete  job  "nodeljob.sh"

Whenever a user attempts to delete a job, the request to delete the job is rejected, and the user is shown an error message.


Form-commit triggers

Unlike the other form triggers, the form-commit trigger fires after a form is committed to the database. Use these triggers for processes that assume (or require) the successful submission of a form. In the case of job forms, the job's name is not set until after the job has been committed to the database; the form-commit trigger is the only way to obtain the name of a new job as part of the process of job creation.

Example 23. The following script, when copied to newjob.sh, shows how to get a job name during the process of job creation, and also reports the status of changelists associated with job fixes.

#!/bin/sh
# newjob.sh - illustrate form-commit trigger

COMMAND=$0
USER=$1
FORM=$2
ACTION=$3

echo $COMMAND: User $USER, formname $FORM, action $ACTION >> log.txt

To use the trigger, add the following line to the trigger table:

sample10  form-commit  job   "newjob.sh %user% %formname% %action%"

Use the %action% variable to distinguish whether or not a change to a job was prompted by a user directly working with a job by means of p4 job, or indirectly by means of fixing the job within the context of p4 fix or the Jobs: field of a changelist.

The simplest case is the creation of a new job (or a change to an existing job) with the p4 job command; the trigger fires, and the script reports the user, the name of the newly-created (or edited) job. In these cases, the %action% variable is null.

The trigger also fires when users add or delete jobs to changelists, and it does so regardless of whether the changed jobs are being manipulated by means of p4 fix, p4 fix -d, or by editing the Jobs: field of the changelist form provided by p4 change or p4 submit form). In these cases, the %action% variable holds the status of the changelist (pending or submitted) to which the jobs are being added or deleted.

Because the %action% variable is not always set, it must be the last argument supplied to any form-commit trigger script.


Triggering on edge servers

As of release 2013.2, your Perforce service can be configured to be distributed, with commit and edge servers. Edge servers have triggers that fire between client and edge server communication, and between edge server and commit server communication. For more information, see Perforce Server Administrator's Guide: Multi-site Deployment.

Triggering to use external authentication

To configure Perforce to work with an external authentication manager (such as LDAP or Active Directory), use authentication triggers (auth-check, auth-check-sso, service-check, and auth-set). These triggers fire on the p4 login and p4 passwd commands, respectively.

Note

You might prefer to enable LDAP authentication by using an LDAP specification. This option is recommended: it is easier to use, no external scripts are required, it provides greater flexibility in defining bind methods, it allows users who are not in the LDAP directory to be authenticated against Perforce's internal user database, and it is more secure. For more information, see "Authentication options" in chapter "Administering Perforce:Superuser Tasks."

Authentication triggers differ from changelist and form triggers in that passwords typed by the user as part of the authentication process are supplied to authentication scripts as standard input; never on the command line. (The only arguments passed on the command line are those common to all trigger types, such as %user%, %clientip%, and so on.)

Warning

Be sure to spell the trigger name correctly when you add the trigger to the trigger table because a misspelling can result in all users being locked out of Perforce.

Be sure to fully test your trigger and trigger table invocation prior to deployment in a production environment.

Contact Perforce Technical Support if you need assistance with restoring access to your server.

The examples in this book are for illustrative purposes only. For a more detailed discussion, including links to sample code for an LDAP environment, see "Setting Up External Authentication Triggers" in the Perforce knowledge base:

http://answers.perforce.com/articles/KB_Article/Setting-Up-External-Authentication-Triggers

You must restart the Perforce server after adding an auth-check (or service-check) trigger in order for it to take effect. You can, however, change an existing auth-check trigger table entry (or trigger script) without restarting the server.

After an auth-check trigger is in place and the server restarted, the Perforce security configurable is ignored; because authentication is now under the control of the trigger script, the server's default mechanism for password strength requirements is redundant.

The following table describes the fields of an authentication trigger definition.

Field

Meaning

type

  • auth-check: Execute an authentication check trigger to verify a user's password against an external password manager during login, or when setting a new password. If an auth-check trigger is present, the Perforce security configurable (and any associated password strength requirement) is ignored, as authentication is now controlled by the trigger script.

    You must restart the Perforce server after adding an auth-check trigger.

  • auth-check-sso: Facilitate a single sign-on user authentication.

  • auth-set: Execute an authentication set trigger to send a new password to an external password manager.

  • service-check: Execute a trigger to verify the password of a service user (or operator user), rather than a standard user. Service check triggers work in the same way that auth-check triggers do.

    You must restart the Perforce server after adding a service-check trigger.

path

Use auth as the path value.

command

The trigger for the Perforce server to run. See the following sections about specific authentication trigger types for more information on when the trigger is fired. In most cases, it is when the p4 login command executes.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

For auth-check and service-check triggers (fired by p4 login from standard users and service/operator users respectively), the user's typed password is supplied to the trigger command as standard input. If the trigger executes successfully, the Perforce ticket is issued. The user name is available as %user% to be passed on the command line.

For auth-check-sso triggers, (fired by p4 login for all users) the output of the client-side script (specified by P4LOGINSSO) is sent to the server-side script in cleartext.

For auth-set triggers, (fired by p4 passwd, but only after also passing an auth-check trigger check) the user's old password and new password are passed to the trigger as standard input. The user name is available as %user% to be passed on the command line.

Auth-check and service-check triggers

Triggers of type auth-check fire when standard users run the p4 login command. Similarly, service-check triggers fire when service users or operator users run the p4 login command. If the script returns 0, login is successful, and a ticket file is created for the user.

The service-check trigger works exactly like an auth-check trigger, but applies only to users whose Type: has been set to service. The service-check trigger type is used by Perforce administrators who want to use LDAP to authenticate other Perforce servers in replicated and other multiserver environments.

Warning

If you are using auth-check triggers, the Perforce superuser must also be able to authenticate against the remote authentication database. (If you, as the Perforce superuser, cannot use the trigger, you may find yourself locked out of your own server, and will have to (temporarily) overwrite your auth-check trigger with a script that always passes in order to resolve the situation.)

Example 24. A trivial authentication-checking script.

All users must enter the password "secret" before being granted login tickets. Passwords supplied by the user are sent to the script on STDIN.

#!/bin/bash
# checkpass.sh - a trivial authentication-checking script

# in this trivial example, all users have the same "secret" password
USERNAME=$1
PASSWORD=secret

# read user-supplied password from stdin
read USERPASS

# compare user-supplied password with correct password
if [ "$USERPASS" = $PASSWORD ]
then
    # Success
    exit 0
fi

# Failure
echo checkpass.sh: password $USERPASS for $USERNAME is incorrect
exit 1

This auth-check trigger fires whenever users run p4 login. To use the trigger, add the following line to the trigger table:

sample11  auth-check  auth  "checkpass.sh %user%"

Users who enter the "secret" password are granted login tickets.


Single signon and auth-check-sso triggers

Triggers of type auth-check-sso fire when standard users run the p4 login command. Two scripts are run: a client-side script is run on the user's workstation, and its output is passed (in plaintext) to the Perforce Server, where the server-side script runs.

  • On the user's client workstation, a script (whose location is specified by the P4LOGINSSO environment variable) is run to obtain the user's credentials or other information verifiable by the Perforce Server. The P4LOGINSSOcontains the name of the client-side script and zero or more of the following trigger variables, passed as parameters to the script: %user%, %serverAddress%, and %P4PORT%. For example:

    export P4LOGINSSO="/path/to/sso-client.sh %user% %serverAddress% %P4PORT%"
    

    Where %user% is the Perforce client user, %serverAddress% is the address of the target Perforce server, and %P4PORT% is an intermediary between the client and the server.

  • On the server, the output of the client-side script is passed to the server-side script as standard input. The server-side script specified in the trigger table runs, and the server returns an exit status of 0 if successful.

    With a distributed configuration in which a proxy or broker acts as an intermediary between the client and the server, the %serverAddress% variable will hold the address/port of the server and the %P4PORT% variable will hold the port of the intermediary. It is up to the script to decide what to do with this information.

Example 25. Interaction between client-side and server-side scripts:

An auth-check-sso trigger fires whenever users run p4 login. The system administrator might add the following line to the trigger table to specify the script that should run on the server side:

sample13  auth-check-sso  auth  "serverside.sh %user%"

and each end user sets the following environment variable on the client side:

export P4LOGINSSO=/usr/local/bin/clientside.sh %serverAddress%

When the user attempts to log on, the P4LOGINSSO script runs on the user's workstation:

##!/bin/bash
# clientside.sh - a client-side authentication script
#
# if we use %serverAddress% in the command-line like this:
#    p4 -E P4LOGINSSO=clientside.sh %serverAddress%
# then this script receives the serverAddress as $1, and the user
# can use it for multiple connections to different Perforce servers.
#
# In this example, we simulate a client-side authentication process
# based on whether the user is connecting to the same Perforce Server
# as is already configured in his or her environment.
# (We also output debugging information to a local file.)

input_saddr=$1

env_saddr=`p4 info | grep "Server address" | awk '{printf "%s", $3}'`

if test "$input_saddr" == "$env_saddr"
  then
    # User is connected to the server specified by P4PORT - pass
    echo "sso pass"; echo pass "$input_saddr" >> debug.txt; exit 0
  else
    # User is attempting to connect to another server - fail
    echo "no pass"; echo fail "$input_saddr" >> debug.txt; exit 1
fi

If the user is connected to the same Perforce Server as specified by P4PORT (that is, if the server address passed from the Server to this script matches the server that appears in the output of a plain p4 info command), client-side authentication succeeds. If the user is connected to another Perforce Server (for example, by running p4 -p host:port login against a different Perforce Server), client-side authentication fails.

The server-side script is as follows:

#!/bin/bash
#
# serverside.sh - a server-side authentication script
#

if test $# -eq 0
  then
    echo "No user name passed in.";
    exit 1;
fi

read msg </dev/stdin

if test "$msg" == ""
  then
    echo "1, no stdin"
    exit 1
fi

if test "$msg" == "sso pass"
  then
    exit 0
  else
    exit 1
fi

In a more realistic example, the end user's P4LOGINSSO script points to a clientside.sh script that contacts an authentication service to obtain a token of some sort. The client-side script then passes this token to Perforce Server's trigger script, and serverside.sh uses the single-signon service to validate the token.

In this example, clientside.sh merely checks whether the user is using the same connection as specified by P4PORT, and the output of clientside.sh is trivially checked for the string "sso pass"; if the string is present, the user is permitted to log on.


Triggering for external authentication

Triggers of type auth-set fire when users (standard users or service users) run the p4 passwd command and successfully validate their old password with an auth-check (or service-check) trigger. The process is as follows:

  1. A user invokes p4 passwd.

  2. The Perforce server prompts the user to enter his or her old password.

  3. The Perforce server fires an auth-check trigger to validate the old password against the external authentication service.

  4. The script associated with the auth-check trigger runs. If the auth-check trigger fails, the process ends immediately: the user is not prompted for a new password, and the auth-set trigger never fires.

  5. If the auth-check trigger succeeds, the server prompts the user for a new password.

  6. The Perforce server fires an auth-set trigger and supplies the trigger script with both the old password and the new password on the standard input, separated by a newline.

    Note

    In most cases, users in an external authentication environment will continue to set their passwords without use of Perforce. The auth-set trigger type is included mainly for completeness.

Because the Perforce server must validate the user's current password, you must have a properly functioning auth-check trigger before attempting to write an auth-set trigger.

Example 26. A trivial authentication-setting script

#!/bin/bash
# setpass.sh - a trivial authentication-setting script

USERNAME=$1

read OLDPASS
read NEWPASS

echo setpass.sh: $USERNAME attempted to change $OLDPASS to $NEWPASS

This auth-set trigger fires after users run p4 passwd and successfully pass the external authentication required by the auth-check trigger. To use the trigger, add the following two lines to the trigger table:

sample11  auth-check  auth  "checkpass.sh %user%"
sample12  auth-set    auth  "setpass.sh %user%"

This trivial example doesn't actually change any passwords; it merely reports back what the user attempted to do.


Triggering to affect archiving

The archive trigger type is used in conjunction with the +X filetype modifier in order to replace the mechanism by which the Perforce Server archives files within the repository. They are used for storing, managing, or generating content archived outside of the Perforce repository. See Execution environment for platform-specific considerations.

The following table describes the fields of an archive trigger definition:

Field

Meaning

type

archive: Execute the script when a user accesses any file with a filetype containing the +X filetype modifier. The script can read, write, or delete files in the archive.

The script is run once per file requested.

For read operations, scripts should deliver the file to the user on standard output. For write operations, scripts receive the file on standard input.

path

A file pattern to match the name of the file being accessed in the archive.

command

The trigger for the Perforce server to run when a file matching path is found in the archive.

Specify the command in a way that allows the Perforce server account to locate and run the command. The command (typically a call to a script) must be quoted, and can take as arguments any argument that your command is capable of parsing, including any applicable Perforce trigger variables.

When your trigger script is stored in the depot, its path must be specified in depot syntax, delimited by percent characters. For example, if your script is stored in the depot as //depot/scripts/myScript.pl, the corresponding value for the command field might be "/usr/bin/perl %//depot/scripts/myScript.pl%". See Executing triggers from the depot for more information.

If the command succeeds, the command's standard output is the file content. If the command fails, the command standard output is sent to the client as the text of a trigger failure error message.

Example 27. An archive trigger

This archive trigger fires when users access files that have the +X (archive) modifier set.

#!/bin/sh
# archive.sh - illustrate archive trigger

OP=$1
FILE=$2
REV=$3

if [ "$OP" = read ]
then
    cat ${FILE}${REV}
fi

if [ "$OP" = delete ]
then
    rm ${FILE}${REV}
fi

if [ "$OP" = write ]
then
    # Create new file from user's submission via stdin
    while read LINE; do
        echo ${LINE} >> ${FILE}${REV}
    done
    ls -t ${FILE}* |
    {
        read first; read second;
        cmp -s $first $second
        if [ $? -eq 0 ]
        then
            # Files identical, remove file, replace with symlink.
            rm ${FILE}${REV}
            ln -s $second $first
        fi
    }
fi

To use the trigger, add the following line to the trigger table:

arch  archive  path  "archive.sh %op% %file% %rev%"

When the user attempts to submit (write) a file of type +X in the specified path, if there are no changes between the current revision and the previous revision, the current revision is replaced with a symlink pointing to the previous revision.


Trigger script variables

You can use trigger script variables to pass data to a trigger script. All data is passed as a string; it is up to the trigger to interpret and use these appropriately.

It is also possible to have the server and trigger communicate using STDIN and STDOUT. For more information, see Communication between a trigger and the server.

The maxError... variables refer to circumstances that prevented the server from completing a command; for example, an operating system resource issue. Note also that client-side errors are not always visible to the server and might not be included in the maxError count.

The terminated and termType variables indicate whether the command exited early and why.

Note

The processing of unknown variables has changed. Previously, unknown variables were removed from the trigger invocation. Currently they are left as is. This preserves the trigger argument ordering, and might be a clue to authors that data they assumed to be available is not.

Argument

Description

Available for type

%action%

Either null or a string reflecting an action taken to a changelist or job.

For example, "pending change 123 added" or "submitted change 124 deleted" are possible %action% values on change forms, and "job000123 created" or "job000123 edited" are possible %action% values for job forms.

form-commit

%argc%

Command argument count.

all except archive

%args%

Command argument string.

all except archive

%argsQuoted%

Command argument string that contains the command arguments as a percent-encoded comma-separated list.

all except archive

%changelist%, %change%

The number of the changelist being submitted. The abbreviated form %change% is equivalent to %changelist%.

A change-submit trigger is passed the pending changelist number; a change-commit trigger receives the committed changelist number.

A shelve-commit or shelve-delete trigger receives the changelist number of the shelf.


change-submit,
change-content,
change-commit,
fix-add,
fix-delete,
form-commit,
shelve-commit,
shelve-delete

%changeroot%

The root path of files submitted.

change-commit

%client%

Triggering user's client workspace name.

all

%clientcwd%

Client's current working directory.

all except archive

%clienthost%

Hostname of the user's workstation (even if connected through a proxy, broker, replica, or an edge server.)

all

%clientip%

The IP address of the user's workstation (even if connected through a proxy, broker, replica, or an edge server.)

all

%clientprog%

The name of the user's client application. For example, P4V, P4Win, etc.

all

%clientversion%

The version of the user's client application.

all

%command%

Command name.

all except archive

%file%

Path of archive file based on depot's Map: field. If the Map: field is relative to P4ROOT, the %file% is a server-side path relative to P4ROOT. If the Map: field is an absolute path, the %file% is an absolute server-side path.

archive

%formfile%

Path to temporary form specification file. To modify the form from an in or out trigger, overwrite this file. The file is read-only for triggers of type save and delete.


form-commit,
form-save,
form-in,
form-out,
form-delete

%formname%

Name of form (for instance, a branch name or a changelist number).


form-commit,
form-save,
form-in,
form-out,
form-delete

%formtype%

Type of form (for instance, branch, change, and so on).


form-commit,
form-save,
form-in,
form-out,
form-delete

%groups%

List of groups to which the user belongs, space-separated.

all except archive

%intermediateService%

A broker or proxy is present.

all except archive

%jobs%

A string of job numbers, expanded to one argument for each job number specified on a p4 fix command or for each job number added to (or removed from) the Jobs: field in a p4 submit, or p4 change form.


fix-add,
fix-delete

%maxErrorSeverity%

One of empty, error, or warning.

all except archive

%maxErrorText%

Error number and text.

all except archive

%maxLockTime%

A user-specified value that specifies the number of milliseconds for the longest permissible database lock. If this varible is set, it means the user has overridden the group setting for this value.

all except archive

%maxResults%

A user-specified value that specifies the amount of data buffered during command execution. If this varible is set, it means the user has overridden the group setting for this value.

all except archive

%maxScanRows%

A user-specified value that specifies the maximum number of rows scanned in a single operation. If this varible is set, it means the user has overridden the group setting for this value.

all except archive

%oldchangelist%

If a changelist is renumbered on submit, this variable contains the old changelist number.

change-commit

%op%

Operation: read, write, or delete.

archive

%peerhost%

If the command was sent through a proxy, broker, replica, or edge server, the hostname of the proxy, broker, replica, or edge server. (If the command was sent directly, %peerhost% matches %clienthost%)

all

%peerip%

If the command was sent through a proxy, broker, replica, or edge server, the IP address of the proxy, broker, replica, or edge server. (If the command was sent directly, %peerip% matches %clientip%)

all

%P4PORT%

The host port to which the client connects. If the client connects to the server through an intermediary, this will hold the port number of the intermediary. If there's no intermediary, this will hold the same value as the %serverAddress% variable.

auth-check-sso (client-side script only)

%quote%

A double quote character.

all

%rev%

Revision of archive file

archive

%serverAddress%

The IP address and port of the Perforce server, passable only in the context of a client-side script specified by P4LOGINSSO.

auth-check-sso (client-side script only)

%serverhost%

Hostname of the Perforce server.

all

%serverpid%

The value of the Perforce server's server.id. See p4 serverid in the P4 Command Reference for details.

all

%serverip%

The IP address of the server.

all

%servername%

The value of the Perforce server's P4NAME.

all

%serverport%

The transport, IP address and port of the Perforce server, in the format prefix:ip_address:port.

prefix can be one of ssl, tcp6, or ssl6. This means that the command p4 -p %serverport% can be used to connect to the server no matter which type of connection the server uses.

all

%serverroot%

The P4ROOT directory of the Perforce server.

all

%serverservices%

A string specifying the role of the server. One of the following:

  • standard

  • replica

  • broker

  • proxy

  • commit-server

  • edge-server

  • forwarding-replica

  • build-server

  • P4AUTH

  • P4CHANGE

all except archive

%serverVersion%

Version string for the server that terminated if the command exited early. Reason for termination is given in %termType%.

all except archive

%submitserverid%

For a change-* trigger in a distributed installation, the server.id of the edge server where the submit was run. See p4 serverid in the P4 Command Reference for details.


change-submit,
change-content,
change-commit,

%terminated%

The value of 0 indicates that the command completed. A value of 1 indicates that the command did not complete.

 

%termType%

The reason for early termination. This might be one of the following:

  • 'p4 monitor terminate'

  • client disconnect

  • maxScanRows

  • maxLockTime

  • maxResults

See also %serverVersion%.

all except archive

%triggerMeta_action%

Command to execute when trigger is fired. Last field of trigger definition. Set only when you run a script from the depot.

all except archive

%triggerMeta_depotFile%

Third field in trigger definition. Its meaning varies with the trigger type. For a change-submit trigger, it is the path for which the trigger is expected to match. For a form-out trigger, it might be the form type to which the trigger is expected to apply. See the description of the trigger types for more information on the meaning of this field.

all except archive

%triggerMeta_name%

Trigger name: first field from trigger definition. Set only when you run a script from the depot.

all except archive

%triggerMeta_trigger%

Trigger type: second field in trigger definition. Set only when you run a script from the depot.

all except archive

%user%

Perforce username of the triggering user.

all

Daemons

Daemons are processes that are called periodically or run continuously in the background. Daemons that use Perforce usually work by examining the server metadata as often as needed and taking action as often as necessary.

Typical daemon applications include:

  • A change review daemon that wakes up every ten minutes to see if any changelists have been submitted to the production depot. If any changelists have been submitted, the daemon sends email to those users who have "subscribed" to any of the files included in those changelists. The message informs them that the files they're interested in have changed.

  • A jobs daemon that generates a report at the end of each day to create a report on open jobs. It shows the number of jobs in each category, the severity each job, and more. The report is mailed to all interested users.

  • A Web daemon that looks for changes to files in a particular depot subdirectory. If new file revisions are found there, they are synced to a client workspace that contains the live web pages.

Daemons can be used for almost any task that needs to occur when Perforce metadata has changed. Unlike triggers, which are used primarily for submission validation, daemons can also be used to write information (that is, submit files) to a depot.

Perforce's change review daemon

The Perforce change review daemon (p4review.py) is available from the Perforce Public Depot:

http://public.perforce.com/wiki/P4Review

The review daemon runs under Python, available at http://www.python.org/. Before you run the review daemon, be sure to read and follow the configuration instructions included in the daemon itself.

Users subscribe to files by calling p4 user, entering their email addresses in the Email: field, and entering any number of file patterns corresponding to files in which they're interested in to the Reviews: field.

User:     sarahm
Email:    [email protected]
Update:   1997/04/29 11:52:08
Access:   1997/04/29 11:52:08
FullName: Sarah MacLonnogan
Reviews:
        //depot/doc/...
        //depot.../README

The change review daemon monitors the files that were included in each newly submitted changelist and emails all users who have subscribed to any files included in a changelist, letting those users know that the files in question have changed.

By including the special path //depot/jobs in the Reviews: field, users can also receive mail from the Perforce change review daemon whenever job data is updated.

The change review daemon implements the following scheme:

  1. p4 counter is used to read and change a variable, called a counter, in the Perforce metadata. The counter used by this daemon, review, stores the number of the latest changelist that's been reviewed.

  2. The Perforce depot is polled for submitted, unreviewed changelists with the p4 review -t review command.

  3. p4 reviews generates a list of reviewers for each of these changelists.

  4. The Python mail module mails the p4 describe changelist description to each reviewer.

  5. The first three steps are repeated every three minutes, or at some other interval configured at the time of installation.

The command used in the fourth step (p4 describe) is a straightforward reporting command. The other commands (p4 review, p4 reviews, and p4 counter) are used almost exclusively by review daemons.

Creating other daemons

You can use p4review.py (see Perforce's change review daemon) as a starting point to create your own daemons, changing it as needed. As an example, another daemon might upload Perforce job information into an external bug tracking system after changelist submission. It would use the p4 review command with a new review counter to list new changelists, and use p4 fixes to get the list of jobs fixed by the newly submitted changelists. This information might then be fed to the external system, notifying it that certain jobs have been completed.

If you write a daemon of your own and would like to share it with other users, you can submit it into the Perforce Public Depot. For more information, go to http://public.perforce.com.

Commands used by daemons

Certain Perforce commands are used almost exclusively by review daemons. These commands are listed in the following table.

Command

Usage

p4 review -c change#

For all changelists between change# and the latest submitted changelist, this command lists the changelists' numbers, creators, and creators' email addresses.

Requires at least review access to run.

p4 reviews -c change# filespec

Lists all users who have subscribed to review the named files or any files in the specified changelist.

p4 counter name [value]

To create a new counter or set the value of an existing counter, you must have review access or greater. To display a counter's value, you must have list access or greater.

If a value argument is not included, p4 counter returns the value of name, or 0 if the counter does not exist.

If a value argument is included, p4 counter sets the value of the name to value. If the counter name does not exist, it is created.

WARNING: The review counters journal, job, and change are used internally by Perforce; use of any of these three names as review numbers could corrupt the Perforce database.

Counters are represented internally as strings. (Prior to release 2008.1, they were stored as signed ints)

p4 counters

List all counters and their values.

p4 changes -m 1 -s submitted

Outputs a single line showing the changelist number of the last submitted changelist, as opposed to the highest changelist number known to the Perforce server.

Daemons and counters

If you're writing a change review daemon or other daemon that deals with submitted changelists, you might also want to keep track of the changelist number of the last submitted changelist, which is the second field in the output of a p4 changes -m 1 -s submitted command.

This is not the same as the output of p4 counter change. The last changelist number known to the Perforce server (the output of p4 counter change) includes pending changelists created by users, but not yet submitted to the depot.

Scripting and buffering

Depending on your platform, the output of individual p4 commands can be fully-buffered (output flushed only after a given number of bytes generated), line-buffered (as on a tty, one line sent per linefeed), or unbuffered.

In general, stdout to a file or pipe is fully buffered, and stdout to a tty is line-buffered. If your trigger or daemon requires line-buffering (or no buffering), you can disable buffering by supplying the -v0 debug flag to the p4 command in question.

If you're using pipes to transfer standard output from a Perforce command (with or without the -v0 flag), you might also experience buffering issues introduced by the kernel, because the -v0 flag can only unbuffer the output of the command itself.