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:
-
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.
-
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.
-
Trigger definitions reviews the basic form of an entry in the trigger table.
-
Execution environment explains how to make sure that your script includes correct information about its execution environment.
-
Executing triggers from the depot describes how to format depot paths if you want to run a trigger from the depot.
-
Communication between a trigger and the server describes how to select the method used for communication and how to parse dictionary input.
-
Using multiple triggers explains how Perforce interprets and processes the trigger table when it includes multiple trigger definitions.
-
Writing triggers to support multiple Perforce servers describes how you can write a trigger so that it is portable across Perforce servers.
-
Triggers and distributed architecture explains the issues you must address when locating triggers on replicas.
For information about debugging triggers, see http://answers.perforce.com/articles/KB/1249
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 |
---|---|
|
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
|
|
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
Please consult the section describing the category of interest to determine which types relate to that trigger. |
|
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. |
|
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
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 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
|
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 inC:\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 aP4USER
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. TheSystem
account may have different environmental configurations (including not just Perforce-related variables, butPATH
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 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:
-
change-submit
(fired on changelist submission, before file transmission) -
change-content
triggers (after changelist submission and file transmission) -
change-commit
triggers (on any automatic changelist renumbering by the server)
Similarly, form triggers of different types are fired in the following order:
-
form-out
(form generation) -
form-in
(changed form is transmitted to the server) -
form-save
(validated form is ready for storage in the Perforce database) -
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.
Triggers and distributed architecture
Triggers installed on the master server must also exist on any of its replicas. Triggers installed on the replicas must have the same execution environment for the triggers and the trigger bodies. This might typically include trigger login tickets or trigger script runtimes like Perl or Python.
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.
You might want to take into consideration file locking behavior associated with submits:
Before committing a changelist, p4 submit briefly locks all files being
submitted. If any file cannot be locked or submitted, the files are left open in a numbered
pending changelist. By default, the files in a failed submit operation are left locked
unless the submit.unlocklocked
configurable is set. Files are unlocked
even if they were manually locked prior to submit if submit fails when
submit.unlocklocked
is set.
The following table describes the fields of a submit trigger. For sample definitions, see the subsequent sections, describing each trigger subtype.
Field |
Meaning |
---|---|
|
|
|
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
|
|
The trigger for the Perforce server to run when a user submits
a changelist that contains any file patterns specified by
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
For |
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
@=
revision
specifier, where change
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.
Note
You may use command type triggers with p4 push and p4 fetch commands.
The following table describes the fields of the
command
trigger.
Field |
Meaning |
---|---|
|
The command to execute is specified in the
|
|
The
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
You cannot create a |
|
The trigger for the Perforce server to run when the condition
implied by
Specify the command in a way that allows the Perforce server
to locate and run the command. The
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
|
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 /; 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 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.
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 |
---|---|
|
|
|
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. |
|
The trigger for the Perforce server to run when a matching
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
|
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 |
---|---|
|
|
|
Use |
|
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
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
For |
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 |
---|---|
|
|
|
The name of the type of form, ( |
|
The trigger for the Perforce server to run when the type of
form specified in the
Specify the command in a way that allows the Perforce server
account to locate and run the command. The
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
For |
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 theName:
field of the user's client workspace specification begins withp4sandbox-
, 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 |
---|---|
|
|
|
Use |
|
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
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
For
For
For |
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. TheP4LOGINSSO
contains 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:
-
A user invokes p4 passwd.
-
The Perforce server prompts the user to enter his or her old password.
-
The Perforce server fires an
auth-check
trigger to validate the old password against the external authentication service. -
The script associated with the
auth-check
trigger runs. If theauth-check
trigger fails, the process ends immediately: the user is not prompted for a new password, and theauth-set
trigger never fires. -
If the
auth-check
trigger succeeds, the server prompts the user for a new password. -
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 |
---|---|
|
The script is run once per file requested.
For |
|
A file pattern to match the name of the file being accessed in the archive. |
|
The trigger for the Perforce server to run when a file
matching
Specify the command in a way that allows the Perforce server
account to locate and run the command. The
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
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 |
---|---|---|
|
Either null or a string reflecting an action taken to a changelist or job.
For example, " |
|
|
Command argument count. |
all except archive |
|
Command argument string. |
all except archive |
|
Command argument string that contains the command arguments as a percent-encoded comma-separated list. |
all except archive |
|
The number of the changelist being submitted. The abbreviated
form
A
A |
|
|
The root path of files submitted. |
|
|
Triggering user's client workspace name. |
all |
|
Client's current working directory. |
all except archive |
|
Hostname of the user's workstation (even if connected through a proxy, broker, replica, or an edge server.) |
all |
|
The IP address of the user's workstation (even if connected through a proxy, broker, replica, or an edge server.) |
all |
|
The name of the user's client application. For example, P4V, P4Win, etc. |
all |
|
The version of the user's client application. |
all |
|
Command name. |
all except archive |
|
Path of archive file based on depot's |
|
|
Path to temporary form specification file. To modify the form
from an |
|
|
Name of form (for instance, a branch name or a changelist number). |
|
|
Type of form (for instance, |
|
|
List of groups to which the user belongs, space-separated. |
all except archive |
|
A broker or proxy is present. |
all except archive |
|
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
|
|
|
One of |
all except archive |
|
Error number and text. |
all except archive |
|
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 |
|
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 |
|
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 |
|
If a changelist is renumbered on submit, this variable contains the old changelist number. |
|
|
Operation: |
|
|
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,
|
all |
|
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,
|
all |
|
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
|
|
|
A double quote character. |
all |
|
Revision of archive file |
|
|
The IP address and port of the Perforce server, passable only
in the context of a client-side script specified by
|
|
|
Hostname of the Perforce server. |
all |
|
The value of the Perforce server's
|
all |
|
The IP address of the server. |
all |
|
The value of the Perforce server's |
all |
|
The transport, IP address and port of the Perforce server, in
the format
|
all |
|
The |
all |
|
A string specifying the role of the server. One of the following:
|
all except archive |
|
Version string for the server that terminated if the command
exited early. Reason for termination is given in
|
all except archive |
|
For a |
|
|
The value of 0 indicates that the command completed. A value of 1 indicates that the command did not complete. |
|
|
The reason for early termination. This might be one of the following:
See also |
all except archive |
|
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 |
|
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 |
|
Trigger name: first field from trigger definition. Set only when you run a script from the depot. |
all except archive |
|
Trigger type: second field in trigger definition. Set only when you run a script from the depot. |
all except archive |
|
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:
-
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. -
The Perforce depot is polled for submitted, unreviewed changelists with the p4 review -t review command.
-
p4 reviews generates a list of reviewers for each of these changelists.
-
The Python mail module mails the p4 describe changelist description to each reviewer.
-
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
|
For all changelists between
Requires at least |
p4 reviews -c |
Lists all users who have subscribed to review the named files or any files in the specified changelist. |
p4 counter |
To create a new counter or set the value of an existing
counter, you must have
If a
If a
WARNING: The review counters
Counters are represented internally as strings. (Prior to
release 2008.1, they were stored as signed
|
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.