├── .dockerignore ├── .gitignore ├── LICENSE ├── README.md ├── clean.sh ├── docker-compose.yml ├── helix-build ├── Dockerfile └── files │ ├── clean.sh │ ├── latest_checkpoint.sh │ ├── replica_setup.sh │ └── restore.sh ├── helix-p4d ├── Dockerfile └── files │ ├── clean.sh │ ├── empty_setup.sh │ ├── init.sh │ ├── latest_checkpoint.sh │ └── restore.sh ├── helix-swarm ├── Dockerfile └── files │ ├── client.p4s │ ├── config.php │ ├── setup.sh │ ├── swarm-trigger.conf │ ├── swarm-trigger.pl │ └── swarm.p4s ├── jira ├── Dockerfile └── docker-entrypoint.sh └── p4-search └── Dockerfile /.dockerignore: -------------------------------------------------------------------------------- 1 | volumes/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | volumes/ 4 | test/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Perforce Software, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helix Docker demos 2 | 3 | Demo the Perforce Server, replicas with Swarm. 4 | 5 | 6 | ## Build and Run 7 | 8 | docker-compose build 9 | docker-compose up 10 | 11 | Swarm server runs on port [5080](http://localhost:5080). 12 | 13 | Perforce server is on port 4000 and replica on port 4001. 14 | 15 | Perforce super user is `super` and password `Passw0rd`. 16 | 17 | ## Backup 18 | 19 | Take a checkpoint (created in `volumes/p4-home/checkpoints/`) 20 | 21 | p4 -C utf8 -p 4000 -u super admin checkpoint -Z 22 | 23 | and then zip up the depots directory e.g. 24 | 25 | cd volumes/p4-home/depots/ 26 | tar cvfz depots.tar.gz * 27 | 28 | Copy both the `volumes/p4-home/depots/depots.tar.gz` and `volumes/p4-home/checkpoints/master.ckp.*.gz ` to a safe location 29 | 30 | ## Restore 31 | 32 | 1. Create a symlink in the `volumes/checkpoints/` directory called `latest` to the 'zipped' checkpoint you wish to restore. 33 | 2. Unzip the `depots.tar.gz` into the `volumes/p4-home/depots/` directory 34 | 3. Restart docker compose. 35 | 36 | Docker will look for a symlink called `latest` on startup and do the rest for you. 37 | 38 | ## Cleanup 39 | 40 | If things stop working try the following: 41 | 42 | Simple cleanup script (intended for OS X) to remove unused docker images and volumes: 43 | 44 | docker-compose kill 45 | ./clean.sh 46 | 47 | Remove all image caches and start from fresh: 48 | 49 | docker-compose build --no-cache 50 | 51 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | docker rm -f $(docker ps -a -q) 2 | docker rmi $(docker images -f "dangling=true" -q) 3 | docker volume rm $(docker volume ls -q) 4 | docker images 5 | ls -lh ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | services: 3 | p4d.helix: 4 | build: 5 | context: helix-p4d 6 | dockerfile: Dockerfile 7 | hostname: p4d 8 | domainname: helix 9 | ports: 10 | - 4000:1666 11 | volumes: 12 | - ./volumes/p4d-home:/p4 13 | tty: true 14 | 15 | build.helix: 16 | build: 17 | context: helix-build 18 | dockerfile: Dockerfile 19 | hostname: build 20 | domainname: helix 21 | ports: 22 | - 4001:1666 23 | depends_on: 24 | - p4d.helix 25 | tty: true 26 | 27 | swarm.helix: 28 | build: 29 | context: helix-swarm 30 | dockerfile: Dockerfile 31 | hostname: swarm 32 | domainname: helix 33 | ports: 34 | - 5080:80 35 | depends_on: 36 | - build.helix 37 | tty: true 38 | 39 | search.helix: 40 | build: 41 | context: p4-search 42 | dockerfile: Dockerfile 43 | hostname: search 44 | domainname: helix 45 | ports: 46 | - 4088:8088 47 | - 8983:8983 48 | depends_on: 49 | - p4d.helix 50 | tty: true -------------------------------------------------------------------------------- /helix-build/Dockerfile: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- 2 | # Docker configuration for Build Replica 3 | # -------------------------------------------------------------------------------- 4 | 5 | FROM ubuntu:xenial 6 | 7 | LABEL vendor="Perforce Software" 8 | LABEL maintainer="Paul Allen (pallen@perforce.com)" 9 | 10 | # Update Ubuntu and add Perforce Package Source 11 | RUN \ 12 | apt-get update && \ 13 | apt-get install -y wget unzip vim iputils-ping && \ 14 | wget -qO - https://package.perforce.com/perforce.pubkey | apt-key add - && \ 15 | echo "deb http://package.perforce.com/apt/ubuntu xenial release" > /etc/apt/sources.list.d/perforce.list && \ 16 | apt-get update 17 | 18 | # -------------------------------------------------------------------------------- 19 | # Docker ENVIRONMENT 20 | # -------------------------------------------------------------------------------- 21 | 22 | # Default Environment 23 | ARG P4PORT=p4d.helix:1666 24 | ARG P4NAME=build 25 | ARG P4TCP=1666 26 | ARG P4USER=super 27 | ARG P4PASSWD=Passw0rd 28 | ARG P4CASE=-C0 29 | ARG P4CHARSET=utf8 30 | ARG SERVICEUSER=service 31 | ARG SERVICEGROUP=unlimited 32 | 33 | # Dynamic Environment 34 | ENV P4PORT=$P4PORT \ 35 | P4NAME=$P4NAME \ 36 | P4TCP=$P4TCP \ 37 | P4USER=$P4USER \ 38 | P4PASSWD=$P4PASSWD \ 39 | P4CASE=$P4CASE \ 40 | P4CHARSET=$P4CHARSET \ 41 | JNL_PREFIX=$P4NAME \ 42 | SERVICEUSER=$SERVICEUSER \ 43 | SERVICEGROUP=$SERVICEGROUP 44 | 45 | # Base Environment 46 | ENV P4HOME=/p4 47 | ENV P4LOGDIR=$P4HOME/log 48 | 49 | # Derived Environment 50 | ENV P4ROOT=$P4HOME/root \ 51 | P4DEPOTS=$P4HOME/depots \ 52 | P4CKP=$P4HOME/checkpoints \ 53 | P4JOURNAL=$P4LOGDIR/journal \ 54 | P4LOG=$P4LOGDIR/p4d.log 55 | 56 | # -------------------------------------------------------------------------------- 57 | # Docker BUILD 58 | # -------------------------------------------------------------------------------- 59 | 60 | # Install Perforce Server 61 | RUN apt-get install -y helix-p4d 62 | 63 | # Add external files 64 | COPY files/clean.sh /usr/local/bin/clean.sh 65 | COPY files/restore.sh /usr/local/bin/restore.sh 66 | COPY files/replica_setup.sh /usr/local/bin/replica_setup.sh 67 | COPY files/latest_checkpoint.sh /usr/local/bin/latest_checkpoint.sh 68 | 69 | RUN \ 70 | chmod +x /usr/local/bin/clean.sh && \ 71 | chmod +x /usr/local/bin/restore.sh && \ 72 | chmod +x /usr/local/bin/replica_setup.sh && \ 73 | chmod +x /usr/local/bin/latest_checkpoint.sh && \ 74 | mkdir -p $P4ROOT && \ 75 | mkdir -p $P4DEPOTS && \ 76 | mkdir -p $P4CKP && \ 77 | mkdir -p $P4LOGDIR 78 | 79 | # Expose Perforce; TCP port and volumes 80 | EXPOSE $P4TCP 81 | VOLUME $P4HOME 82 | 83 | # -------------------------------------------------------------------------------- 84 | # Docker RUN 85 | # -------------------------------------------------------------------------------- 86 | 87 | ENTRYPOINT \ 88 | replica_setup.sh && \ 89 | bash 90 | 91 | HEALTHCHECK \ 92 | --interval=2m \ 93 | --timeout=10s \ 94 | CMD p4 -p $P4TCP info -s > /dev/null || exit 1 95 | -------------------------------------------------------------------------------- /helix-build/files/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Take password as arg or use default 4 | P4PASSWD=${1:-Password!} 5 | 6 | ## Stop Perforce 7 | p4 admin stop 8 | until ! p4 info -s 2> /dev/null; do sleep 1; done 9 | 10 | ## Save current data base 11 | mkdir -p $P4ROOT/save 12 | mv $P4ROOT/db.* $P4ROOT/save 13 | 14 | ## Set server name 15 | echo $P4NAME > $P4ROOT/server.id 16 | 17 | ## Start Perforce 18 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -d 19 | until p4 -p$P4TCP info -s; do sleep 1; done 20 | 21 | replica_setup.sh 22 | -------------------------------------------------------------------------------- /helix-build/files/latest_checkpoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Find latest checkpoint 4 | unset -v latest 5 | for file in "$P4CKP"/$JNL_PREFIX.ckp.*.gz; do 6 | [[ $file -nt $latest ]] && latest=$file 7 | done 8 | 9 | ## If file exists update symlink 10 | if [ "$latest" ]; then 11 | echo "Updating latest symlink to: $latest" 12 | ln -f -s $latest $P4CKP/latest 13 | else 14 | echo "Error: Unable to find a checkpoint" 15 | exit -1 16 | fi 17 | -------------------------------------------------------------------------------- /helix-build/files/replica_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Wait for master to start 4 | echo "Looking for Server [${P4PORT}]..." 5 | until p4 info -s 2> /dev/null; do sleep 1; done 6 | echo "Perforce Server [FOUND]" 7 | 8 | 9 | ## Login 10 | echo $P4PASSWD > pass.txt 11 | p4 login < pass.txt 12 | 13 | # Create service user 14 | echo -e "User: ${SERVICEUSER}\nType: service\nFullName: ${SERVICEUSER}\nEmail: ${SERVICEUSER}@${P4PORT}" | p4 user -i -f 15 | echo -e "Group: ${SERVICEGROUP}\nTimeout: unlimited\nUsers:\n\t${SERVICEUSER}\n" | p4 group -i 16 | 17 | # Configure master with replica details 18 | p4 configure set "${P4NAME}#P4TARGET=${P4PORT}" 19 | p4 configure set "${P4NAME}#server.depot.root=${P4DEPOTS}" 20 | p4 configure set "${P4NAME}#journalPrefix=${P4CKP}/${JNL_PREFIX}" 21 | p4 configure set "${P4NAME}#startup.1=pull -i 1" 22 | p4 configure set "${P4NAME}#startup.2=pull -u -i 1" 23 | p4 configure set "${P4NAME}#startup.3=pull -u -i 1" 24 | 25 | p4 configure set "${P4NAME}#db.replication=readonly" 26 | p4 configure set "${P4NAME}#lbr.replication=readonly" 27 | p4 configure set "${P4NAME}#serviceUser=${SERVICEUSER}" 28 | 29 | # Create Server entry for replica 30 | echo -e "ServerID: ${P4NAME}\nType: server\nServices: build-server\nAddress: ${P4PORT}\n" | p4 server -i 31 | 32 | # Add Service user to protections 33 | p4 protect -o > /$P4HOME/protect.p4s 34 | grep -q -F 'super user ${SERVICEUSER}' /$P4HOME/protect.p4s 35 | if [ $? -ne 0 ]; then 36 | echo "Adding service user to protection table" 37 | echo -e "\tsuper user ${SERVICEUSER} * //..." >> /$P4HOME/protect.p4s 38 | p4 protect -i < /$P4HOME/protect.p4s 39 | fi 40 | 41 | # Login service user 42 | echo -e "${P4PASSWD}\n${P4PASSWD}\n" | p4 passwd ${SERVICEUSER} 43 | p4 -u ${SERVICEUSER} login < pass.txt 44 | 45 | # take seed checkpoint and set as latest 46 | echo "Starting Checkpoint dump..." 47 | rm -f $P4CKP/latest $P4CKP/seed.gz $P4CKP/seed 48 | p4 admin dump > $P4CKP/seed 49 | gzip $P4CKP/seed 50 | ln -f -s $P4CKP/seed.gz $P4CKP/latest 51 | echo "Checkpoint dump complete." 52 | 53 | # rebuild from latest checkpoint 54 | restore.sh 55 | -------------------------------------------------------------------------------- /helix-build/files/restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Test Checkpoint exists 4 | if [ ! -L $P4CKP/latest ]; then 5 | echo "Error: Checkpoint for link $P4CKP/latest not found." 6 | exit -1 7 | fi 8 | 9 | ## Stop Perforce 10 | echo "Stopping Build Server..." 11 | p4 -p$P4TCP admin stop 2> /dev/null 12 | until ! p4 -p$P4TCP info -s 2> /dev/null; do sleep 1; done 13 | echo "Build Server Stopped." 14 | 15 | ## Remove current data base 16 | rm -rf $P4ROOT/* 17 | 18 | ## Set server name 19 | echo $P4NAME > $P4ROOT/server.id 20 | 21 | ## Restore and Upgrade Checkpoint 22 | p4d $P4CASE -r $P4ROOT -jr -z $P4CKP/latest 23 | p4d $P4CASE -r $P4ROOT -xu 24 | 25 | ## Set key environment variables 26 | p4d $P4CASE -r $P4ROOT "-cset ${P4NAME}#server.depot.root=${P4DEPOTS}" 27 | p4d $P4CASE -r $P4ROOT "-cset ${P4NAME}#journalPrefix=${P4CKP}/${JNL_PREFIX}" 28 | 29 | ## Start Perforce 30 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -d 31 | until p4 -p$P4TCP info -s; do sleep 1; done 32 | echo "Build Server Started." 33 | -------------------------------------------------------------------------------- /helix-p4d/Dockerfile: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- 2 | # Docker configuration for P4D 3 | # -------------------------------------------------------------------------------- 4 | 5 | FROM ubuntu:xenial 6 | 7 | LABEL vendor="Perforce Software" 8 | LABEL maintainer="Paul Allen (pallen@perforce.com)" 9 | 10 | # Update Ubuntu and add Perforce Package Source 11 | RUN \ 12 | apt-get update && \ 13 | apt-get install -y wget unzip vim && \ 14 | wget -qO - https://package.perforce.com/perforce.pubkey | apt-key add - && \ 15 | echo "deb http://package.perforce.com/apt/ubuntu xenial release" > /etc/apt/sources.list.d/perforce.list && \ 16 | apt-get update 17 | 18 | # -------------------------------------------------------------------------------- 19 | # Docker ENVIRONMENT 20 | # -------------------------------------------------------------------------------- 21 | 22 | # Default Environment 23 | ARG P4NAME=master 24 | ARG P4TCP=1666 25 | ARG P4USER=super 26 | ARG P4PASSWD=Passw0rd 27 | ARG P4CASE=-C0 28 | ARG P4CHARSET=utf8 29 | 30 | # Dynamic Environment 31 | ENV P4NAME=$P4NAME \ 32 | P4TCP=$P4TCP \ 33 | P4PORT=$P4TCP \ 34 | P4USER=$P4USER \ 35 | P4PASSWD=$P4PASSWD \ 36 | P4CASE=$P4CASE \ 37 | P4CHARSET=$P4CHARSET \ 38 | JNL_PREFIX=$P4NAME 39 | 40 | # Base Environment 41 | ENV P4HOME=/p4 42 | ENV P4LOGDIR=$P4HOME/log 43 | 44 | # Derived Environment 45 | ENV P4ROOT=$P4HOME/root \ 46 | P4DEPOTS=$P4HOME/depots \ 47 | P4CKP=$P4HOME/checkpoints \ 48 | P4JOURNAL=$P4LOGDIR/journal \ 49 | P4LOG=$P4LOGDIR/p4d.log 50 | 51 | # -------------------------------------------------------------------------------- 52 | # Docker BUILD 53 | # -------------------------------------------------------------------------------- 54 | 55 | # Create perforce user and install Perforce Server 56 | RUN \ 57 | apt-get install -y helix-p4d && \ 58 | apt-get install -y helix-swarm-triggers 59 | 60 | # Add external files 61 | COPY files/clean.sh /usr/local/bin/clean.sh 62 | COPY files/restore.sh /usr/local/bin/restore.sh 63 | COPY files/empty_setup.sh /usr/local/bin/empty_setup.sh 64 | COPY files/init.sh /usr/local/bin/init.sh 65 | COPY files/latest_checkpoint.sh /usr/local/bin/latest_checkpoint.sh 66 | 67 | RUN \ 68 | chmod +x /usr/local/bin/clean.sh && \ 69 | chmod +x /usr/local/bin/restore.sh && \ 70 | chmod +x /usr/local/bin/empty_setup.sh && \ 71 | chmod +x /usr/local/bin/init.sh && \ 72 | chmod +x /usr/local/bin/latest_checkpoint.sh 73 | 74 | # Expose Perforce; TCP port and volumes 75 | EXPOSE $P4TCP 76 | VOLUME $P4HOME 77 | 78 | 79 | # -------------------------------------------------------------------------------- 80 | # Docker RUN 81 | # -------------------------------------------------------------------------------- 82 | 83 | ENTRYPOINT \ 84 | init.sh && \ 85 | bash 86 | 87 | HEALTHCHECK \ 88 | --interval=2m \ 89 | --timeout=10s \ 90 | CMD p4 info -s > /dev/null || exit 1 91 | -------------------------------------------------------------------------------- /helix-p4d/files/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Take password as arg or use default 4 | P4PASSWD=${1:-Password!} 5 | 6 | ## Stop Perforce 7 | p4 admin stop 8 | until ! p4 info -s 2> /dev/null; do sleep 1; done 9 | 10 | ## Save current data base 11 | mkdir -p $P4ROOT/save 12 | mv $P4ROOT/db.* $P4ROOT/save 13 | 14 | ## Set server name 15 | echo $P4NAME > $P4ROOT/server.id 16 | 17 | ## Start Perforce 18 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -d 19 | until p4 info -s; do sleep 1; done 20 | 21 | empty_setup.sh 22 | -------------------------------------------------------------------------------- /helix-p4d/files/empty_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start server in Unicode mode 4 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -xi 5 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -d 6 | 7 | ## Create super user and protection 8 | p4 configure set $P4NAME#server.depot.root=$P4DEPOTS 9 | p4 configure set $P4NAME#journalPrefix=$P4CKP/$JNL_PREFIX 10 | p4 user -o | p4 user -i 11 | p4 protect -o | p4 protect -i 12 | -------------------------------------------------------------------------------- /helix-p4d/files/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Setup directories 4 | mkdir -p $P4ROOT 5 | mkdir -p $P4DEPOTS 6 | mkdir -p $P4CKP 7 | mkdir -p $P4LOGDIR 8 | 9 | # Restore checkpoint if symlink latest exists 10 | if [ -L $P4CKP/latest ]; then 11 | echo "Restoring checkpoint..." 12 | restore.sh 13 | rm $P4CKP/latest 14 | else 15 | echo "Create empty or start existing server..." 16 | empty_setup.sh 17 | fi 18 | 19 | echo "Perforce Server starting..." 20 | until p4 info -s 2> /dev/null; do sleep 1; done 21 | echo "Perforce Server [RUNNING]" 22 | 23 | ## Remove all triggers 24 | echo "Triggers:" | p4 triggers -i 25 | -------------------------------------------------------------------------------- /helix-p4d/files/latest_checkpoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Find latest checkpoint 4 | unset -v latest 5 | for file in "$P4CKP"/$JNL_PREFIX.ckp.*.gz; do 6 | [[ $file -nt $latest ]] && latest=$file 7 | done 8 | 9 | ## If file exists update symlink 10 | if [ "$latest" ]; then 11 | echo "Updating latest symlink to: $latest" 12 | ln -f -s $latest $P4CKP/latest 13 | else 14 | echo "Error: Unable to find a checkpoint" 15 | exit -1 16 | fi 17 | -------------------------------------------------------------------------------- /helix-p4d/files/restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Test for latest link 4 | if [ ! -L $P4CKP/latest ]; then 5 | echo "Link not found - looking for checkpoint" 6 | /usr/local/bin/latest_checkpoint.sh 7 | fi 8 | 9 | ## Test Checkpoint exists 10 | if [ ! -L $P4CKP/latest ]; then 11 | echo "Error: Checkpoint for link $P4CKP/latest not found." 12 | exit -1 13 | fi 14 | 15 | ## Stop Perforce 16 | #p4 admin stop 17 | #until ! p4 info -s 2> /dev/null; do sleep 1; done 18 | 19 | ## Remove current data base 20 | rm -rf $P4ROOT/* 21 | 22 | ## Set server name 23 | echo $P4NAME > $P4ROOT/server.id 24 | 25 | ## Restore and Upgrade Checkpoint 26 | p4d $P4CASE -r $P4ROOT -jr -z $P4CKP/latest 27 | p4d $P4CASE -r $P4ROOT -xu 28 | 29 | ## Set key environment variables 30 | p4d $P4CASE -r $P4ROOT "-cset security=2" 31 | p4d $P4CASE -r $P4ROOT "-cset ${P4NAME}#server.depot.root=${P4DEPOTS}" 32 | p4d $P4CASE -r $P4ROOT "-cset ${P4NAME}#journalPrefix=${P4CKP}/${JNL_PREFIX}" 33 | 34 | ## Start Perforce 35 | p4d $P4CASE -r$P4ROOT -p$P4TCP -L$P4LOG -J$P4JOURNAL -d 36 | -------------------------------------------------------------------------------- /helix-swarm/Dockerfile: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- 2 | # Docker configuration for Swarm 3 | # -------------------------------------------------------------------------------- 4 | 5 | FROM ubuntu:xenial 6 | 7 | LABEL vendor="Perforce Software" 8 | LABEL maintainer="Paul Allen (pallen@perforce.com)" 9 | 10 | # Update Ubuntu and add Perforce Package Source 11 | RUN \ 12 | apt-get update && \ 13 | apt-get install -y wget curl unzip vim && \ 14 | wget -qO - https://package.perforce.com/perforce.pubkey | apt-key add - && \ 15 | echo "deb http://package.perforce.com/apt/ubuntu xenial release" > /etc/apt/sources.list.d/perforce.list && \ 16 | apt-get update 17 | 18 | # Create swarm user and install Helix Swarm 19 | RUN \ 20 | useradd --user-group --home-dir /home/swarm --create-home --uid 1000 swarm && \ 21 | apt-get install -y helix-cli && \ 22 | apt-get install -y -f helix-swarm=2019.1-1829459~xenial 23 | 24 | # -------------------------------------------------------------------------------- 25 | # Docker ENVIRONMENT 26 | # -------------------------------------------------------------------------------- 27 | 28 | # Default Environment 29 | ARG P4PORT=p4d.helix:1666 30 | ARG P4USER=super 31 | ARG P4PASSWD=Passw0rd 32 | ARG SWARMHOST=swarm.helix 33 | ARG SWARMUSER=swarm 34 | ARG SWARMPASSWD=$P4PASSWD 35 | ARG SWARMTOKEN=00000000-0000-0000-0000-000000000000 36 | ARG MAILHOST=swarm.helix 37 | 38 | # Configure Swarm Environment 39 | ENV P4PORT=$P4PORT \ 40 | P4USER=$P4USER \ 41 | P4PASSWD=$P4PASSWD \ 42 | SWARMHOST=$SWARMHOST \ 43 | SWARMUSER=$SWARMUSER \ 44 | SWARMPASSWD=$SWARMPASSWD \ 45 | SWARMTOKEN=$SWARMTOKEN \ 46 | MAILHOST=$MAILHOST 47 | 48 | EXPOSE 80 49 | 50 | # Add external files 51 | COPY files/setup.sh /opt/perforce/swarm/setup.sh 52 | COPY files/config.php /opt/perforce/swarm/data/config.php 53 | 54 | # Configure Apache/Swarm 55 | RUN \ 56 | chmod +x /opt/perforce/swarm/setup.sh && \ 57 | chown -R www-data:www-data /opt/perforce/swarm/data && \ 58 | a2dissite 000-default && \ 59 | mkdir -p /opt/perforce/swarm/data/queue/tokens && \ 60 | touch /opt/perforce/swarm/data/queue/tokens/$SWARMTOKEN 61 | 62 | USER swarm 63 | COPY files/swarm.p4s /home/swarm/swarm.p4s 64 | COPY files/client.p4s /home/swarm/client.p4s 65 | COPY files/swarm-trigger.pl /home/swarm/swarm-trigger.pl 66 | COPY files/swarm-trigger.conf /home/swarm/swarm-trigger.conf 67 | 68 | # -------------------------------------------------------------------------------- 69 | # Docker RUN 70 | # -------------------------------------------------------------------------------- 71 | 72 | USER root 73 | ENTRYPOINT \ 74 | until p4 info -s 2> /dev/null; do sleep 1; done && \ 75 | echo "Perforce Server [FOUND]" && \ 76 | until p4 -p build.helix:1666 info -s 2> /dev/null; do sleep 1; done && \ 77 | echo "Build Server [FOUND]" && \ 78 | cron start && \ 79 | /opt/perforce/swarm/setup.sh && \ 80 | bash 81 | 82 | HEALTHCHECK \ 83 | --interval=2m \ 84 | --timeout=10s \ 85 | CMD curl --fail http://localhost/api/version || exit 1 86 | -------------------------------------------------------------------------------- /helix-swarm/files/client.p4s: -------------------------------------------------------------------------------- 1 | Client: swarm.triggers.ws 2 | Owner: swarm 3 | Root: /home/swarm 4 | Options: noallwrite noclobber nocompress unlocked nomodtime normdir 5 | SubmitOptions: submitunchanged 6 | LineEnd: local 7 | View: 8 | //depot/triggers/... //swarm.triggers.ws/... 9 | -------------------------------------------------------------------------------- /helix-swarm/files/config.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'hostname' => 'swarm.helix', 5 | ), 6 | 'p4' => array( 7 | 'port' => 'p4d.helix:1666', 8 | 'user' => 'swarm', 9 | 'password' => 'Passw0rd', 10 | ), 11 | 'mail' => array( 12 | 'transport' => array( 13 | 'host' => 'swarm.helix', 14 | ), 15 | ), 16 | 'security' => array( 17 | 'require_login' => false, // defaults to true 18 | ), 19 | ); 20 | -------------------------------------------------------------------------------- /helix-swarm/files/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /opt/perforce/swarm/sbin/configure-swarm.sh --non-interactive \ 4 | --p4port ${P4PORT} \ 5 | --swarm-user ${SWARMUSER} --swarm-passwd ${SWARMPASSWD} \ 6 | --swarm-host ${SWARMHOST} --email-host ${MAILHOST} \ 7 | --create --create-group --force \ 8 | --super-user ${P4USER} --super-passwd ${P4PASSWD} 9 | 10 | ## Login 11 | echo $P4PASSWD > pass.txt 12 | p4 login < pass.txt 13 | 14 | ## Add Swarm user to protections 15 | p4 protect -o > /$P4HOME/protect.p4s 16 | grep -q -F 'super user ${SWARMUSER}' /$P4HOME/protect.p4s 17 | if [ $? -ne 0 ]; then 18 | echo "Adding Swarm user to protection table" 19 | echo -e "\tadmin user ${SWARMUSER} * //..." >> /$P4HOME/protect.p4s 20 | p4 protect -i < /$P4HOME/protect.p4s 21 | fi 22 | 23 | ## Remove all triggers 24 | echo "Triggers:" | p4 triggers -i 25 | 26 | ## Check in the Swarm trigger Perl script 27 | p4 set P4CLIENT=swarm.triggers.ws 28 | p4 client -i < /home/swarm/client.p4s 29 | sed -i "s|%SWARMHOST%|$SWARMHOST|g" /home/swarm/swarm-trigger.conf 30 | sed -i "s|%SWARMTOKEN%|$SWARMTOKEN|g" /home/swarm/swarm-trigger.conf 31 | p4 add /home/swarm/swarm-trigger.pl 32 | p4 add /home/swarm/swarm-trigger.conf 33 | p4 submit -d "Swarm trigger" 34 | 35 | ## Install the Swarm triggers 36 | p4 triggers -i < /home/swarm/swarm.p4s 37 | 38 | ## Remove URL property and set to external docker localhost URL 39 | p4 property -d -n P4.Swarm.URL -s 0 40 | p4 property -a -n P4.Swarm.URL -v http://localhost:5080 41 | 42 | 43 | echo "Swarm setup finished." -------------------------------------------------------------------------------- /helix-swarm/files/swarm-trigger.conf: -------------------------------------------------------------------------------- 1 | SWARM_HOST="http://%SWARMHOST%" 2 | SWARM_TOKEN="%SWARMTOKEN%" 3 | ADMIN_USER="" 4 | ADMIN_TICKET_FILE="" 5 | P4="p4" -------------------------------------------------------------------------------- /helix-swarm/files/swarm-trigger.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # Perforce Swarm Trigger Script 4 | # 5 | # @copyright 2015 Perforce Software. All rights reserved. 6 | # @version / 7 | # 8 | # This script is used to push Perforce events into Swarm or to restrict committing 9 | # changes that are associated to reviews in Swarm. This script requires certain 10 | # variables defined to operate correctly (described below). 11 | # 12 | # This script should be executed in one of the following ways: 13 | # 14 | # swarm-trigger.pl -t -v [-p ] [-r] [-g ] 15 | # [-c ] 16 | # 17 | # swarm-trigger.pl -o 18 | # 19 | # Swarm trigger is meant to be called from a Perforce trigger. It should be placed 20 | # on the Perforce Server machine. Check the output from 'swarm-trigger.pl -o' for 21 | # an example configuration that can be copied into Perforce triggers. 22 | # 23 | # The -t specifies the Swarm trigger type, one of: job, user, userdel, 24 | # group, groupdel, changesave, shelve, commit, ping, enforce, strict. 25 | # 26 | # The -v specifies the ID value, e.g. Perforce change number for 'commit' 27 | # type, username for 'user' type etc. 28 | # 29 | # The -p specifies the optional (recommended) P4PORT, only intended for 30 | # '-t enforce' or '-t strict'. 31 | # 32 | # The -r will make the Swarm trigger perform checks only on changes that are in 33 | # review (only used with '-t enforce' or '-t strict'). 34 | # 35 | # The -g specifies optional group to exclude for '-t strict' or '-t enforce'. 36 | # Members of this group, or subgroups thereof will not be subject to these triggers. 37 | # 38 | # The -c specifies optional config file to source variables from 39 | # (see below). Anything defined in the will override variables 40 | # defined in the default config files (see below). 41 | # 42 | # The -o will output the sample trigger lines that can be copied into Perforce 43 | # triggers. 44 | # 45 | # You can utilize one of the following default configuration files to define the 46 | # variables needed: 47 | # 48 | # /etc/perforce/swarm-trigger.conf 49 | # /opt/perforce/etc/swarm-trigger.conf 50 | # swarm-trigger.conf (in the same directory as this script) 51 | # 52 | # The following config variables are recognized and utilized by the Swarm trigger 53 | # script: 54 | # 55 | # SWARM_HOST hostname of your Swarm instance, with leading http:// 56 | # or https:// 57 | # 58 | # SWARM_TOKEN the token used when talking to Swarm to offer some security 59 | # to obtain the value, log in to Swarm as a super user and 60 | # select 'About Swarm' 61 | # 62 | # ADMIN_USER for enforcing reviewed changes, optionally specify the 63 | # Perforce user with admin privileges (to read keys); 64 | # if not set, will use whatever Perforce user is set in 65 | # environment 66 | # 67 | # ADMIN_TICKET_FILE for enforcing reviewed changes, optionally specify the 68 | # location of the p4tickets file if different from the 69 | # default '$HOME/.p4tickets' 70 | # ensure this user is a member of a group with an 'unlimited' 71 | # or very long timeout; then, manually login as this user 72 | # from the Perforce server machine to set the ticket 73 | # 74 | # P4_PORT for enforcing reviewed changes, optionally specify the 75 | # Perforce port 76 | # note that this value will be ignored if the trigger script 77 | # is called with '-p' 78 | # 79 | # P4 for '-t strict' and '-t enforce', we use the 'p4' utility 80 | # if 'p4' isn't available in the PATH of the environment in 81 | # which Perforce trigger scripts run, specify the full path 82 | # of the utility here (default value 'p4') 83 | # 84 | # EXEMPT_FILE_COUNT changes with total number of files equal or greater than 85 | # this number (if set to a positive integer) will not be 86 | # subject to strict or enforce triggers 87 | # 88 | # EXEMPT_EXTENSIONS comma-separated list of file extensions; changes having 89 | # only files with these extensions will not be subject to 90 | # strict or enforce triggers 91 | # 92 | # VERIFY_SSL either 0 or 1, where 1 will validate the SSL certificate 93 | # of the Swarm web server, and 0 will skip validation 94 | # allowing the use of self-signed certificates. 95 | # 96 | # These config variables can also be specified inside this script itself (under 97 | # the "%config" variable below). Note that for the later option, any values 98 | # defined in the default config files (or one specified via -c) will override 99 | # what is set here. In addition, if you replace or update this script to a new 100 | # version, please ensure you preserve your changes. 101 | # 102 | # Example of configuration file: 103 | # 104 | # SWARM_HOST="http://my-swarm-host" 105 | # SWARM_TOKEN="MY-UUID-STYLE-TOKEN" 106 | # ADMIN_USER="" 107 | # ADMIN_TICKET_FILE="" 108 | # P4="p4" 109 | # 110 | # For '-t strict' and '-t enforce', P4 variable must be specified if your 'p4' 111 | # utility is not in the PATH of the environment in which Perforce trigger script 112 | # runs. For other types, SWARM_HOST and SWARM_TOKEN variables must be specified. 113 | # 114 | # Please report any bugs or feature requests to . 115 | 116 | # Specify the fallback config values here. Be aware that they will be overridden 117 | # by values of matching variables in default config files or the one specified 118 | # via -c as described above. 119 | my %config = ( 120 | SWARM_HOST => 'http://%SWARMHOST%', 121 | SWARM_TOKEN => '%SWARMTOKEN%', 122 | ADMIN_USER => '', 123 | ADMIN_TICKET_FILE => '', 124 | P4_PORT => '', 125 | P4 => 'p4', 126 | EXEMPT_FILE_COUNT => 0, 127 | EXEMPT_EXTENSIONS => '', 128 | COOKIES => '', 129 | VERIFY_SSL => 1 130 | ); 131 | 132 | # DO NOT EDIT PAST THIS LINE ------------------------------------------------ # 133 | 134 | require 5.008; 135 | 136 | use strict; 137 | use warnings; 138 | use Cwd 'abs_path'; 139 | use Digest::MD5; 140 | use File::Basename; 141 | use File::Temp qw(tempfile tempdir mktemp); 142 | use Getopt::Std; 143 | use POSIX qw(SIGINT SIG_BLOCK SIG_UNBLOCK); 144 | use Scalar::Util qw(looks_like_number); 145 | use Sys::Syslog; 146 | 147 | sub get_fstat_blocks ($$); 148 | sub fstat_blocks_differ ($$); 149 | sub get_ktext_files_to_fix ($$); 150 | sub get_raw_digest ($$$); 151 | sub escape_shell_arg ($); 152 | sub run; 153 | sub run_quiet; 154 | sub get_trigger_entries; 155 | sub parse_config; 156 | sub get_insensitive_pattern; 157 | sub usage (;$); 158 | sub safe_fork; 159 | sub error ($;$$); 160 | 161 | # Introspect a little about ourselves and where we live 162 | my $ME = basename($0); 163 | my $ABS_ME = abs_path($0); 164 | my $MY_PATH = dirname($ABS_ME); 165 | my $IS_WIN = $^O eq 'MSWin32'; 166 | my $HAVE_TINY = eval { 167 | require HTTP::Tiny; 168 | import HTTP::Tiny; 169 | 1 170 | }; 171 | 172 | # Setup logging; syslog won't actually connect till we have something to say 173 | openlog($ME, 'nofatal', 0); 174 | 175 | # Show short usage if there are no arguments 176 | usage('short') unless scalar @ARGV; 177 | 178 | # Parse out command line arguments 179 | my %args; 180 | error('Unknown or invalid argument provided') and usage('short') 181 | unless getopts('t:v:p:g:rc:oh', \%args); 182 | 183 | # Generate friendlier keys for the commonly used args 184 | @args{qw(type value p4port config_file exclude_group)} = @args{qw(t v p c g)}; 185 | $args{review_changes_only} = defined $args{r}; 186 | 187 | # Show full usage if help is requested 188 | usage() if $args{h}; 189 | 190 | # Dump just the trigger entries if -o was passed 191 | print get_trigger_entries() and exit 0 if $args{o}; 192 | 193 | # Looks like we're doing this for real; ensure we have a -t and -v with data. 194 | error('No event type supplied') and usage('short') 195 | unless defined $args{type} && length $args{type}; 196 | error('No ID value supplied') and usage('short') 197 | unless defined $args{value} && length $args{value}; 198 | 199 | # Parse any config files 200 | parse_config(); 201 | 202 | # Strict and enforce triggers happen inline as we need to know the result 203 | if ($args{type} eq 'enforce' || $args{type} eq 'strict') { 204 | # Sanity test p4 command. 205 | run($config{P4}, '-V'); 206 | die "$ME: p4 is not set properly; please contact your administrator.\n" 207 | unless $? == 0; 208 | 209 | # Set up how we call p4. 210 | my @P4_CMD = ($config{P4}, "-zprog=p4($ME)"); 211 | my $P4_PORT = defined $args{p4port} ? $args{p4port} : $config{P4_PORT}; 212 | 213 | push @P4_CMD, '-p', $P4_PORT 214 | if length $P4_PORT; 215 | push @P4_CMD, '-u', $config{ADMIN_USER} 216 | if length $config{ADMIN_USER}; 217 | 218 | $ENV{P4TICKETS} = $config{ADMIN_TICKET_FILE} 219 | if length $config{ADMIN_TICKET_FILE}; 220 | 221 | # Set character-set explicitly if talking to a unicode server. 222 | if (grep(/\.{3} unicode enabled/, run(@P4_CMD, '-ztag', 'info'))) { 223 | push @P4_CMD, '-C', 'utf8'; 224 | } 225 | 226 | # Verify our credentials. 227 | run(@P4_CMD, 'login', '-s'); 228 | if ($? != 0) { 229 | error( 230 | "Invalid login credentials to [$P4_PORT] within this trigger script;" 231 | . ' please contact your administrator.', 232 | "$args{type}: reject change $args{value}:" 233 | . " invalid login credentials to [$P4_PORT]" 234 | ); 235 | exit 1; 236 | } 237 | 238 | # If we have an exclude group, check if the change user is a member. 239 | if (defined $args{exclude_group} && length $args{exclude_group}) { 240 | # Obtain the user from the change. 241 | my @users = map { m/^\.{3} User (.+)/ ? ($1) : () } 242 | run(@P4_CMD, '-ztag', 'change', '-o', $args{value}); 243 | 244 | # Look for groups the user belongs to and exit if we find a match. 245 | if (scalar @users and grep { chomp; $_ eq $args{exclude_group} } 246 | run(@P4_CMD, 'groups', '-i', '-u', $users[0]) 247 | ) { 248 | syslog(5, "$args{type}: accept change $args{value}: " 249 | . "'$users[0]' belongs to exempt group '$args{exclude_group}'"); 250 | exit 0; 251 | } 252 | } 253 | 254 | # If we are configured with files max limit, check if we are over. 255 | $config{EXEMPT_FILE_COUNT} = 0 unless looks_like_number($config{EXEMPT_FILE_COUNT}); 256 | if ($config{EXEMPT_FILE_COUNT} > 0) { 257 | # Get change client (needed for the following fstat command). 258 | my @client = map { m/^\.{3} Client (.+)/ ? ($1) : () } 259 | run(@P4_CMD, '-ztag', 'change', '-o', $args{value}); 260 | 261 | # Obtain files count in the change. 262 | my @filesCount = map { m/^\.{3} totalFileCount (\d+)/ ? ($1) : () } 263 | run(@P4_CMD, '-c', $client[0], 'fstat', '-m1', '-r', 264 | '-T', 'totalFileCount', '-e', "$args{value}", '-Ro', '//...'); 265 | 266 | # If the change has at least exempt-file-count total number of files, 267 | # log a notice and exit happily. 268 | if ($filesCount[0] >= $config{EXEMPT_FILE_COUNT}) { 269 | syslog(5, "$args{type}: accept change $args{value}: " 270 | . "change has >= $config{EXEMPT_FILE_COUNT} files"); 271 | exit 0; 272 | } 273 | } 274 | 275 | # Prepare list of exempt file extensions from the config. The resulting list 276 | # is either empty or contains trimmed, non-empty extensions with no leading dot. 277 | my @exemptExtensions = map { s/^\s*\.?|\s+$//g; length $_ ? $_ : () } 278 | split ',', $config{EXEMPT_EXTENSIONS}; 279 | 280 | # If we have exempt extensions, skip the rest if all files in the change have 281 | # one of the these extensions. 282 | if (scalar @exemptExtensions) { 283 | # Get change client (needed for the following fstat command). 284 | my @client = map { m/^\.{3} Client (.+)/ ? ($1) : () } 285 | run(@P4_CMD, '-ztag', 'change', '-o', $args{value}); 286 | 287 | # Check if change being committed contains a file with extension other 288 | # than exempt. Note we treat exempt extensions case in-sensitive. 289 | my @extensionPatterns = get_insensitive_pattern(@exemptExtensions); 290 | my $pattern = 'depotFile~=\\\\.\\(' . join('\\|', @extensionPatterns) . '\\)\\$'; 291 | my @files = map { m/^\.{3} depotFile (.+)/ ? ($1) : () } 292 | run(@P4_CMD, '-c', $client[0], 'fstat', '-m1', '-T', 'depotFile', 293 | '-F', "^($pattern)", '-e', $args{value}, '-Ro', '//...'); 294 | 295 | # Exit happily if change has no other files. 296 | if (scalar @files == 0) { 297 | syslog(5, "$args{type}: accept change $args{value}: " 298 | . 'change contains only files of exempt types (' 299 | . join(',', @exemptExtensions) . ')'); 300 | exit 0; 301 | } 302 | } 303 | 304 | # Search for the review key based on the encoded change number 305 | (my $changeSearch = $args{value}) =~ s/(.)/3$1/g; 306 | my $reviewKey = (run(@P4_CMD, 'search', "1301=$changeSearch"))[0]; 307 | 308 | # Detect if there is any problem with the command 309 | if ($? != 0) { 310 | error( 311 | "Error searching Perforce for reviews involving this change" 312 | . "($args{value}); please contact your administrator.", 313 | "$args{type}: reject change $args{value}: error ($?) from [" 314 | . join(' ', @P4_CMD) . " search 1301=$changeSearch]" 315 | ); 316 | exit $?; 317 | } 318 | 319 | # Detect if no review is found. 320 | unless (defined $reviewKey) { 321 | # If enforcement is only set for reviews, exit happy for changes. 322 | exit 0 if $args{review_changes_only}; 323 | 324 | error( 325 | "Cannot find a Swarm review associated with this change ($args{value}).", 326 | "$args{type}: reject change $args{value}: no Swarm review found", 327 | 5 328 | ); 329 | exit 1; 330 | } 331 | 332 | # Strip the trailing newline from the review key. 333 | chomp $reviewKey; 334 | 335 | # Detect if the key name is badly formatted. 336 | if ($reviewKey !~ /swarm\-review\-([0-9a-f]+)/) { 337 | error( 338 | "Bad review key for this change ($args{value});" 339 | . " please contact your administrator.", 340 | "$args{type}: reject change $args{value}:" 341 | . " bad Swarm review key ($reviewKey)" 342 | ); 343 | exit 1; 344 | } 345 | 346 | # Calculate the human-friendly review ID. 347 | my $reviewId = hex('ffffffff') - hex($1); 348 | 349 | # Obtain the JSON value of the associated review. 350 | my $reviewJson = (run(@P4_CMD, 'counter', '-u', $reviewKey))[0]; 351 | 352 | # Detect if there is an error or no value for the key (stale index?). 353 | if ($? != 0 || !defined $reviewJson) { 354 | error( 355 | "Cannot find Swarm review data for this change ($args{value}).", 356 | "$args{type}: reject change $args{value}: empty value for ($reviewKey)", 357 | 4 358 | ); 359 | exit 1; 360 | } 361 | 362 | # Locate the change inside the review's associated changes. 363 | if ($reviewJson !~ m/\"changes\":[^\]]*[^\d]\Q$args{value}\E[^\d]/i) { 364 | error( 365 | "This change ($args{value}) is not associated with" 366 | . " its linked Swarm review $reviewId.", 367 | "$args{type}: reject change $args{value}:" 368 | . " change not part of $reviewKey ($reviewId)", 369 | 5 370 | ); 371 | exit 1; 372 | } 373 | 374 | # Obtain review state and see if it's 'approved'. 375 | $reviewJson =~ m/\"state\":\"([^"]*)\"/i; 376 | if ($1 ne 'approved') { 377 | error( 378 | "Swarm review $reviewId for this change ($args{value})" 379 | . " is not approved ($1).", 380 | "$args{type}: reject change $args{value}:" 381 | . " $reviewKey ($reviewId) not approved ($1)", 382 | 5 383 | ); 384 | exit 1; 385 | } 386 | 387 | # For -t strict, check that the change's content matches that of its review. 388 | if ($args{type} eq 'strict') { 389 | my %reviewFstat = get_fstat_blocks(\@P4_CMD, $reviewId); 390 | my %changeFstat = get_fstat_blocks(\@P4_CMD, $args{value}); 391 | 392 | if (!%reviewFstat || !%changeFstat) { 393 | error( 394 | "Error obtaining fstat output for this change ($args{value})" 395 | . " or its associated review ($reviewId);" 396 | . " please contact your administrator.", 397 | "$args{type}: reject change $args{value}: error obtaining" 398 | . " fstat output for either change or review ($reviewId)" 399 | ); 400 | exit 1; 401 | } 402 | 403 | # Before we compare review/change fstat blocks, we fix digests for 404 | # ktext type files (re-calculate digests with the keywords not 405 | # expanded), if necessary. 406 | for my $filespec (get_ktext_files_to_fix(\%reviewFstat, \%changeFstat)) { 407 | # recalculate digest with keywords not expanded 408 | $reviewFstat{$filespec}{digest} = get_raw_digest( 409 | \@P4_CMD, $filespec, $reviewId 410 | ); 411 | $changeFstat{$filespec}{digest} = get_raw_digest( 412 | \@P4_CMD, $filespec, $args{value} 413 | ); 414 | 415 | # no need to keep going if the digests of recently updated 416 | # ktext files don't match. 417 | last unless $reviewFstat{$filespec}{digest} eq $changeFstat{$filespec}{digest}; 418 | } 419 | 420 | # compare review/change fstat data. 421 | if (fstat_blocks_differ(\%reviewFstat, \%changeFstat)) { 422 | error( 423 | "The content of this change ($args{value}) does not match the" 424 | . " content of the associated Swarm review ($reviewId).", 425 | "$args{type}: reject change $args{value}:" 426 | . " content does not match review ($reviewId)", 427 | 5 428 | ); 429 | exit 1; 430 | } 431 | } 432 | 433 | # Return success at this point. 434 | exit 0; 435 | } 436 | 437 | # Sanity check global variables we need for posting events to Swarm. 438 | if (!length $config{SWARM_HOST} || $config{SWARM_HOST} eq 'http://my-swarm-host') { 439 | error( 440 | "SWARM_HOST is not set properly; please contact your administrator.", 441 | "$args{type}: SWARM_HOST empty or default" 442 | ); 443 | exit 1; 444 | } 445 | if (!length $config{SWARM_TOKEN} || $config{SWARM_TOKEN} eq 'MY-UUID-STYLE-TOKEN') { 446 | error( 447 | "SWARM_TOKEN is not set properly; please contact your administrator.", 448 | "$args{type}: SWARM_TOKEN empty or default" 449 | ); 450 | exit 1; 451 | } 452 | 453 | # Ping trigger deals with archive files whose content is sent/received via STDIN/STDOUT. 454 | # Although we don't care about the content (it doesn't change), we should read STDIN 455 | # for write operations and print to STDOUT for read operations to avoid potential errors. 456 | if ($args{type} eq 'ping') { 457 | my @tmp = 458 | if $args{value} eq 'write'; 459 | 460 | print STDOUT "Placeholder file for testing Swarm triggers. Do not modify the content of this file." 461 | if $args{value} eq 'read'; 462 | } 463 | 464 | # For other Swarm trigger types, post the event to Swarm asynchronously (detach to the background). 465 | # Note we don't presently background on Windows; only *nix systems. 466 | if (!$IS_WIN) { 467 | # Flush output immediately; no buffering. 468 | local $| = 1; 469 | 470 | # Safely fork the process - returns child pid to the parent process and 0 471 | # to the child process. 472 | my $pid; 473 | eval { $pid = safe_fork(); }; 474 | error("Failed to fork: $@") and exit 1 if $@; 475 | 476 | # Exit parent. 477 | exit 0 if $pid; 478 | 479 | # Close STDOUT and STDERR to allow detaching. 480 | if ($args{type} ne "ping") { 481 | close STDOUT; 482 | close STDERR; 483 | } 484 | } 485 | 486 | # The host really really aught to lead with http already, but add it if needed. 487 | $config{SWARM_HOST} = 'http://' . $config{SWARM_HOST} 488 | if $config{SWARM_HOST} !~ /^http/; 489 | 490 | # We assume SWARM_HOST and SWARM_TOKEN are properly set at this point. 491 | my $SWARM_QUEUE = "$config{SWARM_HOST}/queue/add/$config{SWARM_TOKEN}"; 492 | 493 | # Swarm accepts the POST data in a non-standard format, with both values in a list. 494 | my $options = { content => "$args{type},$args{value}" }; 495 | 496 | # We only expect to be setting Cookies in a test environment. 497 | if (exists $config{COOKIES} && $config{COOKIES} ne '') { 498 | $options->{headers} = { 499 | 'Content-Type' => 'application/x-www-form-urlencoded', 500 | 'Cookie' => $config{COOKIES} 501 | }; 502 | } else { 503 | $options->{headers} = { 504 | 'Content-Type' => 'application/x-www-form-urlencoded' 505 | }; 506 | } 507 | 508 | # Force verification of SSL certificates if VERIFY_SSL is set. 509 | # HTTP::Tiny does not do this by default. 510 | my %attributes; 511 | if ($config{VERIFY_SSL} == 1) { 512 | $attributes{'verify_SSL'} = 1; 513 | } 514 | 515 | my $failure = ""; 516 | if ($HAVE_TINY) { 517 | my $response = HTTP::Tiny->new(%attributes)->post($SWARM_QUEUE, $options); 518 | if ($response->{status} == 599 && $config{VERIFY_SSL} == 1) { 519 | $failure = "Error: ($response->{status}/$response->{reason}) (probably invalid SSL certificate) trying to post [$args{type},$args{value}] to [$SWARM_QUEUE]"; 520 | } elsif ($response->{status} != 200) { 521 | $failure = "Error: ($response->{status}/$response->{reason}) trying to post [$args{type},$args{value}] to [$SWARM_QUEUE]"; 522 | } elsif ($response->{content} ne "") { 523 | # It's possible to get a 200 back if we're talking to the wrong web server. We expect 524 | # no content to be returned by the call to Swarm, so if we get a 200, but also get 525 | # content returned (such as "It Works!"), then probably something is wrong. 526 | $failure = "Error: Unexpected content returned by [$SWARM_QUEUE], is this a Swarm server? ($response->{content})"; 527 | } 528 | } else { 529 | # The tiny module is not available, so use curl 530 | my @curl_cmd=qw(curl --max-time 10 -sS); 531 | # Disable verification of certificates 532 | if($config{VERIFY_SSL} != 1){ 533 | push(@curl_cmd,"--insecure"); 534 | } 535 | if($config{COOKIES}){ 536 | push(@curl_cmd, "--cookie"); 537 | push(@curl_cmd, $config{COOKIES}); 538 | } 539 | push(@curl_cmd, "--data",); 540 | my $output = run( 541 | @curl_cmd, 542 | "$args{type},$args{value}", 543 | $SWARM_QUEUE 544 | ); 545 | if ($? != 0) { 546 | $failure = "Error: ($?) trying to post [$args{type},$args{value}] via [curl] to [$SWARM_QUEUE]"; 547 | } elsif ($output) { 548 | $failure = "Error: Unexpected output from swarm trigger via [curl] to [$SWARM_QUEUE]. [$output]"; 549 | } 550 | } 551 | 552 | # Always return success to avoid affecting Perforce users, unless this was a ping command. 553 | if ($failure) { 554 | syslog(3, $failure); 555 | if ($args{type} eq "ping") { 556 | printf("$failure\n"); 557 | exit 1; 558 | } 559 | } 560 | exit 0; 561 | 562 | #============================================================================== 563 | # Local Functions 564 | #============================================================================== 565 | 566 | # Returns a hash with fstat data for a given change. 567 | # Hash will contain filespec for each file in the fstat output in key and 568 | # filespec, type and digest in values. 569 | sub get_fstat_blocks ($$) { 570 | my ($cmd, $change) = @_; 571 | 572 | die 'First argument to '. (caller(0))[3] ." must be an array reference\n" 573 | unless ref $cmd eq 'ARRAY'; 574 | 575 | # Run fstat command to collect data. 576 | my @fstat = run( 577 | @$cmd, 'fstat', '-Ol', '-T', 'depotFile,headType,digest', "@=$change" 578 | ); 579 | 580 | # Early exit with an empty block if command failed. 581 | return {} if $? != 0; 582 | 583 | # Group fstat output by depotFile into blocks. 584 | my $file; 585 | my %blocks; 586 | for (@fstat) { 587 | if (m/^\.\.\. depotFile (.+)/) { 588 | $file = $1; 589 | } 590 | 591 | m/^\.\.\. ([^ ]+) (.+)/; 592 | $blocks{$file}{$1} = $2 if $file; 593 | } 594 | 595 | return %blocks; 596 | } 597 | 598 | # Compares two fstat blocks and return 1 if they differ or 0 otherwise. 599 | # Fstat blocks do not differ if they have same amount of blocks (files) and 600 | # depotFile, headType and digest data match for each block representing the 601 | # same file. 602 | sub fstat_blocks_differ ($$) { 603 | my ($a, $b) = @_; 604 | 605 | die 'First argument to '. (caller(0))[3] ." must be a hash reference\n" 606 | unless ref $a eq 'HASH'; 607 | die 'Second argument to '. (caller(0))[3] ." must be a hash reference\n" 608 | unless ref $b eq 'HASH'; 609 | 610 | # Blocks differ if they have different number of keys. 611 | return 1 unless keys %{$a} == keys %{$b}; 612 | 613 | # We go through blocks in $a and will try to find matching data in $b. 614 | for my $file (keys %{$a}) { 615 | # Early exit if key is not present in block $b. 616 | return 1 unless defined $b->{$file}; 617 | 618 | # Compare the values for 'depotFile', 'headType' and 'digest'. 619 | for my $field ('depotFile', 'headType', 'digest') { 620 | if (defined $a->{$file}->{$field} && defined $b->{$file}->{$field}) { 621 | return 1 unless $a->{$file}->{$field} eq $b->{$file}->{$field}; 622 | } 623 | } 624 | } 625 | 626 | return 0; 627 | } 628 | 629 | # Returns a list with ktext files present in passed fstat blocks whose 630 | # digests should be recalculated (with keywords not expanded) before these 631 | # blocks get compared via fstat_blocks_differ() since in this script we 632 | # consider two ktext files different only when they differ in 'raw' state 633 | # (keywords not expanded). 634 | # 635 | # Since calculating digests can be expensive, we want to do it only if 636 | # necessary, i.e. only if other pieces in fstat blocks are same (number of 637 | # files, file types and digests except for ktext files). 638 | sub get_ktext_files_to_fix ($$) { 639 | my ($reviewFstat, $changeFstat) = @_; 640 | 641 | die 'First argument to '. (caller(0))[3] ." must be a hash reference\n" 642 | unless ref $reviewFstat eq 'HASH'; 643 | die 'Second argument passed to '. (caller(0))[3] ." must be a hash reference\n" 644 | unless ref $changeFstat eq 'HASH'; 645 | 646 | # No need to explore more if blocks have different number of keys. 647 | return () unless scalar keys %{$reviewFstat} == scalar keys %{$changeFstat}; 648 | 649 | # We go through review blocks and will try to find matching data in 650 | # change blocks. 651 | my @filesToFix; 652 | for my $filespec (keys %{$reviewFstat}) { 653 | # No need to fix if review file is not present in change. 654 | return () unless exists $changeFstat->{$filespec}; 655 | 656 | # No need to fix if file types differ. 657 | return () unless $reviewFstat->{$filespec}->{headType} eq $changeFstat->{$filespec}->{headType}; 658 | 659 | # No need to fix if digests for non-ktext files differ. 660 | my $isKtext = $reviewFstat->{$filespec}->{headType} =~ m/kx?text|.+\+.*k/i; 661 | my $digestsMatch = $reviewFstat->{$filespec}->{digest} eq $changeFstat->{$filespec}->{digest}; 662 | return () unless ($isKtext || $digestsMatch); 663 | 664 | push @filesToFix, $filespec if $isKtext; 665 | } 666 | 667 | # Making it this far means that both review and change fstat blocks 668 | # have same files with same types and all non-ktext file digests match. 669 | # Return list of ktext files we encountered (they are present in both 670 | # change/review blocks). 671 | return @filesToFix; 672 | } 673 | 674 | # Returns digest of a given file with keywords not expanded. 675 | sub get_raw_digest ($$$) { 676 | my ($cmd, $filespec, $change) = @_; 677 | 678 | die 'First argument to '. (caller(0))[3] ." must be an array reference\n" 679 | unless ref $cmd eq 'ARRAY'; 680 | 681 | my $dir = tempdir(CLEANUP => 1); 682 | my $filename = mktemp( $dir . '/XXXXXX' ); 683 | 684 | # Temporarily capture STDERR in a variable. 685 | open OLDERR, ">&STDERR"; 686 | local (*RH, *WH); 687 | pipe RH, WH; 688 | open STDERR, ">&WH" or die "Cannot open STDERR: $!"; 689 | print OLDERR ''; # to avoid error about OLDERR not being used 690 | 691 | # Run the command to save file with keywords not expanded in temporary 692 | # location. This will produce an error on old servers (<2012.2) as 693 | # '-k' option is not available. 694 | run(@$cmd, 'print', '-k', '-o', $filename, "$filespec@=$change"); 695 | 696 | # Read 2 lines of error output from the previous command. If there were 697 | # no errors, this would wait indefinitely. To avoid it, we artifically 698 | # produce 2 blank lines of error (doesn't have impact on behaviour). 699 | print STDERR "\n\n"; 700 | close WH; 701 | my $error = . ; 702 | close RH; 703 | 704 | # Restore STDERR. 705 | open STDERR, ">&", OLDERR or die "Cannot restore STDERR: $!"; 706 | 707 | # Check if the the command failed due to 'Invalid option: -k'. 708 | if ($? != 0) { 709 | die "Cannot print into file: $filename [$?]" 710 | unless $error =~ m/Invalid option\: \-k/i; 711 | 712 | # Try to replace keywords manually. 713 | my $tmpFile = mktemp( $dir . '/XXXXXX' ); 714 | run(@$cmd, 'print', '-o', $tmpFile, "$filespec@=$change"); 715 | 716 | open(my $in, '<', $tmpFile); 717 | open(my $out, '>', $filename); 718 | while (my $line = <$in>) { 719 | $line =~ s/\$(Id|Header|Date|DateTime|Change|File|Revision|Author)\:[^\$]+\$/\$$1\$/g; 720 | print $out $line; 721 | } 722 | close $in; 723 | close $out; 724 | } 725 | 726 | open my $in, '<', $filename; 727 | return Digest::MD5->new->addfile($in)->hexdigest; 728 | } 729 | 730 | # Escapes a string to be used as a shell argument. 731 | sub escape_shell_arg ($) { 732 | my ($arg) = @_; 733 | 734 | if ($IS_WIN) { 735 | $arg =~ s/["%!]/ /; 736 | } else { 737 | $arg =~ s/\'/\'\\\'/; 738 | } 739 | 740 | # under Windows, if arg ends with odd number of slashes, add one more 741 | $arg =~ m/(\\*)$/; 742 | if ($IS_WIN && length($1) % 2) { 743 | $arg .= '\\'; 744 | } 745 | 746 | # wrap argument in quotes 747 | $arg = $IS_WIN 748 | ? '"' . $arg . '"' 749 | : '\'' .$arg . '\''; 750 | 751 | return $arg; 752 | } 753 | 754 | # Runs the command specified in parameters and returns the array with lines 755 | # of command output. 756 | sub run { 757 | my $cmd = join q{ }, map { escape_shell_arg($_) } @_; 758 | return `$cmd`; 759 | } 760 | 761 | sub run_quiet { 762 | my $cmd = join q{ }, map { escape_shell_arg($_) } @_; 763 | return $IS_WIN ? `$cmd 1> NUL 2> NUL` : `$cmd &>/dev/null`; 764 | } 765 | 766 | # Parses the config files in fixed locations (if they exist) and saves the 767 | # values into %config hash. 768 | sub parse_config { 769 | my @candidates = ( 770 | !$IS_WIN ? '/etc/perforce/swarm-trigger.conf' : '', 771 | !$IS_WIN ? '/opt/perforce/etc/swarm-trigger.conf' : '', 772 | "$MY_PATH/swarm-trigger.conf", 773 | $args{config_file} 774 | ); 775 | 776 | foreach my $file (@candidates) { 777 | if (defined $file && length $file && -e $file && open(my $fh, '<', "$file")) { 778 | while (my $line = <$fh>) { 779 | chomp $line; 780 | $line =~ s/#.*$//; 781 | next unless $line =~ /=/; 782 | $line =~ s/^\s+|\s+$//g; 783 | 784 | my ($key, $value) = split(/=/, $line, 2); 785 | $key =~ s/^['"]?|['"]?\s*$//g; # trim key's whitespace/quotes 786 | $value =~ s/^\s*['"]?|['"]?$//g; # ditto for the value 787 | $config{$key} = $value if length $value; 788 | } 789 | } 790 | } 791 | } 792 | 793 | # Turn input string(s) into regex patterns for case-insensitive look up. 794 | # Example: 'String' will be turned into '[sS][tT][rR][iI][nN][gG]'. 795 | sub get_insensitive_pattern { 796 | my @patterns = (); 797 | for my $string (@_) { 798 | my @pattern = map { '\\[\\\\' . (lc $_) . '\\\\' . (uc $_) . '\\]' } 799 | split("", $string); 800 | 801 | push @patterns, join('', @pattern); 802 | } 803 | 804 | return @patterns; 805 | } 806 | 807 | # Returns string with formatted trigger lines that can be copied into 808 | # Perforce triggers. 809 | sub get_trigger_entries { 810 | my $script = $IS_WIN 811 | ? "%quote%$^X%quote% %quote%$ABS_ME%quote%" 812 | : "%quote%$ABS_ME%quote%"; 813 | 814 | my $config = $args{config_file} 815 | ? ' -c %quote%'. abs_path($args{config_file}) .'%quote%' 816 | : ''; 817 | 818 | # Define the trigger entries suitable for this script; replace depot 819 | # paths as appropriate. 820 | return < -v \\ 849 | [-p ] [-r] [-g ] [-c ] 850 | $ME -o 851 | -t: specify the Swarm trigger type (e.g. job, shelve, commit) 852 | -v: specify the ID value 853 | -p: specify optional (recommended) P4PORT, only intended for 854 | '-t enforce' or '-t strict' 855 | -r: when using '-t strict' or '-t enforce', only apply this check 856 | to changes that are in review. 857 | -g: specify optional group to exclude for '-t enforce' or 858 | '-t strict'; members of this group, or subgroups thereof will 859 | not be subject to these triggers 860 | -c: specify optional config file to source variables 861 | -o: convenience flag to output the trigger lines 862 | 863 | EOU 864 | 865 | exit 99 if $short; 866 | 867 | print STDERR < is passed, that file will be sourced too. 890 | 891 | * For 'enforce' triggers (enforce that a change to be submitted is tied to an 892 | approved review), or 'strict' triggers (verify that the content of a change to 893 | be submitted matches the content of its associated approved review), uncomment 894 | the appropriate lines and replace DEPOT_PATH as appropriate. For additional 895 | paths to check, increment the trigger name suffix so that each trigger name is 896 | named uniquely. 897 | 898 | * For 'enforce' or 'strict' triggers, you can optionally specify a group whose 899 | members will not be subject to these triggers. 900 | 901 | * For 'enforce' or 'strict' triggers, if your Perforce Server is SSL-enabled, 902 | add the \"ssl:\" protocol prefix to \"%serverport%\". 903 | 904 | EON 905 | 906 | exit 99; 907 | } 908 | 909 | # Forks the process safely with protection against interrupts while forking. 910 | # Code borrowed from Net::Server::Daemonize. 911 | sub safe_fork { 912 | # block signal for fork. 913 | my $sigset = POSIX::SigSet->new(SIGINT); 914 | POSIX::sigprocmask(SIG_BLOCK, $sigset) 915 | or die "Can't block SIGINT for fork: [$!]"; 916 | 917 | my $pid = fork(); 918 | die "Couldn't fork: [$!]" unless defined $pid; 919 | 920 | $SIG{'INT'} = 'DEFAULT'; # make SIGINT kill us as it did before. 921 | 922 | POSIX::sigprocmask(SIG_UNBLOCK, $sigset) 923 | or die "Can't unblock SIGINT for fork: [$!]"; 924 | 925 | return $pid; 926 | } 927 | 928 | # Helper subroutine to log and print a given message into standard error: 929 | # Parameter 1 is the print message (required) 930 | # Parameter 2 is the log message (optional), when missing, = param 1 931 | # Parameter 3 is the log priority (optional), defaults to 3 (error) 932 | sub error ($;$$) { 933 | # Check the input and provide default values for optional parameters. 934 | my $printError = $_[0]; 935 | my $logError = defined $_[1] ? $_[1] : $printError; 936 | my $logLevel = defined $_[2] ? $_[2] : 3; 937 | 938 | syslog($logLevel, $logError); 939 | print STDERR "$printError\n"; 940 | } 941 | 942 | __END__ 943 | 944 | =head1 NAME 945 | 946 | Perforce Swarm Trigger Script - script for Perforce triggers 947 | 948 | =head1 DESCRIPTION 949 | 950 | This script is used to push Perforce events into Swarm or to restrict committing 951 | changes that are associated to reviews in Swarm. For full details, please read 952 | the comments in the script file. 953 | 954 | =cut 955 | -------------------------------------------------------------------------------- /helix-swarm/files/swarm.p4s: -------------------------------------------------------------------------------- 1 | Triggers: 2 | swarm.job form-commit job "perl %//depot/triggers/swarm-trigger.pl% -t job -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 3 | swarm.user form-commit user "perl %//depot/triggers/swarm-trigger.pl% -t user -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 4 | swarm.userdel form-delete user "perl %//depot/triggers/swarm-trigger.pl% -t userdel -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 5 | swarm.group form-commit group "perl %//depot/triggers/swarm-trigger.pl% -t group -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 6 | swarm.groupdel form-delete group "perl %//depot/triggers/swarm-trigger.pl% -t groupdel -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 7 | swarm.changesave form-save change "perl %//depot/triggers/swarm-trigger.pl% -t changesave -c %//depot/triggers/swarm-trigger.conf% -v %formname%" 8 | swarm.shelve shelve-commit //... "perl %//depot/triggers/swarm-trigger.pl% -t shelve -c %//depot/triggers/swarm-trigger.conf% -v %change%" 9 | swarm.commit change-commit //... "perl %//depot/triggers/swarm-trigger.pl% -t commit -c %//depot/triggers/swarm-trigger.conf% -v %change%" 10 | -------------------------------------------------------------------------------- /jira/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/cptactionhank/docker-atlassian-jira/blob/master/Dockerfile 2 | 3 | FROM openjdk:8-alpine 4 | 5 | # Configuration variables. 6 | ENV JIRA_HOME /var/atlassian/jira 7 | ENV JIRA_INSTALL /opt/atlassian/jira 8 | ENV JIRA_VERSION 7.9.2 9 | 10 | # Install Atlassian JIRA and helper tools and setup initial home 11 | # directory structure. 12 | RUN set -x \ 13 | && apk add --no-cache curl xmlstarlet bash ttf-dejavu libc6-compat \ 14 | && mkdir -p "${JIRA_HOME}" \ 15 | && mkdir -p "${JIRA_HOME}/caches/indexes" \ 16 | && chmod -R 700 "${JIRA_HOME}" \ 17 | && chown -R daemon:daemon "${JIRA_HOME}" \ 18 | && mkdir -p "${JIRA_INSTALL}/conf/Catalina" \ 19 | && curl -Ls "https://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-core-${JIRA_VERSION}.tar.gz" | tar -xz --directory "${JIRA_INSTALL}" --strip-components=1 --no-same-owner \ 20 | && curl -Ls "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.45.tar.gz" | tar -xz --directory "${JIRA_INSTALL}/lib" --strip-components=1 --no-same-owner "mysql-connector-java-5.1.45/mysql-connector-java-5.1.45-bin.jar" \ 21 | && rm -f "${JIRA_INSTALL}/lib/postgresql-9.1-903.jdbc4-atlassian-hosted.jar" \ 22 | && curl -Ls "https://jdbc.postgresql.org/download/postgresql-42.2.1.jar" -o "${JIRA_INSTALL}/lib/postgresql-42.2.1.jar" \ 23 | && chmod -R 700 "${JIRA_INSTALL}/conf" \ 24 | && chmod -R 700 "${JIRA_INSTALL}/logs" \ 25 | && chmod -R 700 "${JIRA_INSTALL}/temp" \ 26 | && chmod -R 700 "${JIRA_INSTALL}/work" \ 27 | && chown -R daemon:daemon "${JIRA_INSTALL}/conf" \ 28 | && chown -R daemon:daemon "${JIRA_INSTALL}/logs" \ 29 | && chown -R daemon:daemon "${JIRA_INSTALL}/temp" \ 30 | && chown -R daemon:daemon "${JIRA_INSTALL}/work" \ 31 | && sed --in-place "s/java version/openjdk version/g" "${JIRA_INSTALL}/bin/check-java.sh" \ 32 | && echo -e "\njira.home=$JIRA_HOME" >> "${JIRA_INSTALL}/atlassian-jira/WEB-INF/classes/jira-application.properties" \ 33 | && touch -d "@0" "${JIRA_INSTALL}/conf/server.xml" 34 | 35 | # Use the default unprivileged account. This could be considered bad practice 36 | # on systems where multiple processes end up being executed by 'daemon' but 37 | # here we only ever run one process anyway. 38 | USER daemon:daemon 39 | 40 | # Expose default HTTP connector port. 41 | EXPOSE 8080 42 | 43 | # Set volume mount points for installation and home directory. Changes to the 44 | # home directory needs to be persisted as well as parts of the installation 45 | # directory due to eg. logs. 46 | VOLUME ["/var/atlassian/jira", "/opt/atlassian/jira/logs"] 47 | 48 | # Set the default working directory as the installation directory. 49 | WORKDIR /var/atlassian/jira 50 | 51 | COPY "docker-entrypoint.sh" "/" 52 | ENTRYPOINT ["/docker-entrypoint.sh"] 53 | 54 | # Run Atlassian JIRA as a foreground process by default. 55 | CMD ["/opt/atlassian/jira/bin/start-jira.sh", "-fg"] 56 | -------------------------------------------------------------------------------- /jira/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check if the `server.xml` file has been changed since the creation of this 4 | # Docker image. If the file has been changed the entrypoint script will not 5 | # perform modifications to the configuration file. 6 | if [ "$(stat -c "%Y" "${JIRA_INSTALL}/conf/server.xml")" -eq "0" ]; then 7 | if [ -n "${X_PROXY_NAME}" ]; then 8 | xmlstarlet ed --inplace --pf --ps --insert '//Connector[@port="8080"]' --type "attr" --name "proxyName" --value "${X_PROXY_NAME}" "${JIRA_INSTALL}/conf/server.xml" 9 | fi 10 | if [ -n "${X_PROXY_PORT}" ]; then 11 | xmlstarlet ed --inplace --pf --ps --insert '//Connector[@port="8080"]' --type "attr" --name "proxyPort" --value "${X_PROXY_PORT}" "${JIRA_INSTALL}/conf/server.xml" 12 | fi 13 | if [ -n "${X_PROXY_SCHEME}" ]; then 14 | xmlstarlet ed --inplace --pf --ps --insert '//Connector[@port="8080"]' --type "attr" --name "scheme" --value "${X_PROXY_SCHEME}" "${JIRA_INSTALL}/conf/server.xml" 15 | fi 16 | if [ "${X_PROXY_SCHEME}" = "https" ]; then 17 | xmlstarlet ed --inplace --pf --ps --insert '//Connector[@port="8080"]' --type "attr" --name "secure" --value "true" "${JIRA_INSTALL}/conf/server.xml" 18 | xmlstarlet ed --inplace --pf --ps --update '//Connector[@port="8080"]/@redirectPort' --value "${X_PROXY_PORT}" "${JIRA_INSTALL}/conf/server.xml" 19 | fi 20 | if [ -n "${X_PATH}" ]; then 21 | xmlstarlet ed --inplace --pf --ps --update '//Context/@path' --value "${X_PATH}" "${JIRA_INSTALL}/conf/server.xml" 22 | fi 23 | fi 24 | 25 | exec "$@" 26 | -------------------------------------------------------------------------------- /p4-search/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | LABEL vendor="Perforce Software" 4 | LABEL maintainer="Paul Allen (pallen@perforce.com)" 5 | 6 | # Update Ubuntu and add Perforce Package Source 7 | RUN \ 8 | apt-get update && \ 9 | apt-get install -y wget unzip vim netcat lsb-core net-tools rsyslog && \ 10 | apt-get install -y openjdk-8-jdk && \ 11 | wget -qO - https://package.perforce.com/perforce.pubkey | apt-key add - && \ 12 | echo "deb http://package.perforce.com/apt/ubuntu xenial release" > /etc/apt/sources.list.d/perforce.list && \ 13 | apt-get update 14 | 15 | RUN apt-get install -y helix-cli 16 | 17 | ADD http://ftp.perforce.com/perforce/r16.1/bin.java/p4search.tgz /p4search.tgz 18 | 19 | RUN tar xfz /p4search.tgz 20 | 21 | ## TODO - add triggers: 22 | # SEARCH.change form-commit change "%//depot/p4search/install/search-queue.sh% -t change -v %formname%" 23 | # SEARCH.commit change-commit //... "%//depot/p4search/install/search-queue.sh% -t commit -v %change%" --------------------------------------------------------------------------------