Cómo configurar el gancho de Gitlab para validar un git push a control remoto

Tenemos un process DevOps en el que las personas realizan compras o actualizaciones desde la sucursal maestra remota, crean una nueva sucursal de trabajo local desde la maestra y comienzan a trabajar en la sucursal local.

1) Tengo que poner restricciones que eviten que las personas presionen directamente a la twig maestra remota. En su lugar, las personas deben enviar los cambios a sus sucursales locales a la misma sucursal en el control remoto, después de lo cual un administrador o revisor de códigos lo fusionará en el maestro remoto.

2) Necesito un gancho que garantice que haya un número válido de boleto o número de Gitlab en el formulario, digamos por ejemplo # PROJECTNAME123 antes de permitir que su inserción proceda a sus sucursales remotas (antes de la revisión del código y la fusión en el maestro remoto). Además, no deben poder presionar si el ticket no existe o aún no está abierto.

Ya he creado un enlace de pre-recepción de Bash utilizando la información de los dos sitios web siguientes, pero reciben una llamada, pero todavía permiten que el git push llegue al server incluso cuando no paso un número de ticket / emisión de Gitlab.

https://github.com/Praqma/git-hooks/commit/2aa087fada0b0da51724f37a902362ddd78e168f

http://blog.hgomez.net/2015/03/02/Gitlab-custom-hooks-Bash-Way.html

El siguiente es el script pre-recibido y un script de function bash al que llama.

pre-recibir (sin extensión)

#!/usr/bin/env bash # source /var/opt/gitlab/git-data/repositories/Product-common/ProductCommonParent.git/custom_hooks/pre-receive-functions.sh # enforced custom commit message format while read old_revision new_revision refname ; do process_revision done exit 0 

prereceive-functions.sh

 #!/usr/bin/env bash # regexp="#[0-9]\+" grep_msg() { grepped=$( echo $message | grep -i $regexp ) } process_revision () { #revisions=$(git rev-list $old_revision..$new_revision) echo "In pre-receive hook. Just before retrieving the revisions" if [ "$old_revision" -eq 0 ]; then # list everything reachable from new_revision but not any heads revisions=$(git rev-list $(git for-each-ref --format='%(refname)' refs/heads/* | sed 's/^/\^/') $new_revision) else revisions=$(git rev-list $old_revision..$new_revision) fi echo "In pre-receive hook. Just before IFS" IFS='\n' read -ra array <<< "$revisions" for rid in "${!array[@]}"; do revision=${array[rid]} message=$(git cat-file commit $revision | sed '1,/^$/d') grepped=$(echo $message | grep -i "#[0-9]\+") grep_msg() if [ -z "$grepped" ] ; then grepped_none=$(echo $message | grep -i "#none") if [ -n "$grepped_none" ] ; then echo "Warning, you are committing without a ticket reference" >&1 else echo "You have not included a ticket reference" >&2 exit 1 fi fi done } 

La siguiente es la salida cuando bash presionar (estoy presionando desde un shell de Git Bash en Windows 8.1 a un Fedora Core 24 que tiene Gitlab instalado):

 xxx@xxxxx-HP MINGW64 ~/Documents/DevOps Re-Engineering/ProductCommonParent (ProductCommonParent002) $ git push Counting objects: 3, done. Delta compression using up to 8 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 369 bytes | 0 bytes/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: In pre-receive hook. Just before retrieving the revisions remote: In pre-receive hook. Just before IFS remote: remote: To create a merge request for ProductCommonParent002, visit: remote: http://localhost/Product-common/ProductCommonParent/merge_requests/new?merge_request%5Bsource_branch%5D=ProductCommonParent002 remote: To http://192.168.56.101/Product-common/ProductCommonParent.git * [new branch] ProductCommonParent002 -> ProductCommonParent002 

Nota: Gitlab y sus dependencies, incluyendo git, están instaladas en el mismo sistema Fedora Core 24 Linux.

Apreciaré la ayuda rápida para superar esto. Muchas gracias de antemano por tu ayuda.

1) Las restricciones ya están disponibles por defecto para cada twig restringida pnetworkingeterminada de cualquier proyecto nuevo o git repo. Estas restricciones se aplican a usuarios no administradores y no root de Gitlab.

2) Implementamos las reglas para verificar que nuestros desarrolladores cumplan con nuestras políticas y processs de desarrollo al escribir una aplicación Java Spring boot de CommandLineRunner que llamó a la API de Gitlab. Esta aplicación fue empaquetada como un file jar.

Nos aseguramos de que un desarrollador debe tener un número de ticket válido como parte de su post de compromiso de git antes de que pueda enviarlo con éxito a la contraparte remota de su sucursal de trabajo. Este boleto válido debe asignarse a él, tener un hito válido y tener la label correcta (ya sea NUEVA FUNCIÓN, ERROR, TAREA, etc.) seleccionada para que el empuje sea exitoso.

Nos integramos con los ganchos de git en el server de Gitlab mediante el uso de un script de shell bash que ejecutó el file jar y permitió o no la request de inserción basada en la salida de la aplicación java. Este script de shell, que es una adaptación de http://blog.hgomez.net/2015/03/02/Gitlab-custom-hooks-Bash-Way.html , se puede encontrar a continuación:

 #!/bin/bash # # pre-receive hook for Commit Check # COMPANY_EMAIL="mycorp.org" readonly PROGNAME=$(basename $0) readonly PROGDIR=$(readlink -m $(dirname $0)) IS_MERGE=0 check_single_commit() { COMMIT_CHECK_STATUS=1 echo "Repo >> $REPOSITORY_BASENAME" if [[ "$COMMIT_MESSAGE" == "Merge branch"* ]]; then COMMIT_CHECK_STATUS=0 IS_MERGE=1 else workFlowResult=`java -jar -Dspring.config.location=/home/gitlab/gitlab_custom_hooks/application.properties /home/gitlab/gitlab_custom_hooks/gitlab-tool.jar -prercv "$COMMIT_AUTHOR" "$COMMIT_MESSAGE" "$REPOSITORY_BASENAME"` echo "COMMIT_AUTHOR=$COMMIT_AUTHOR, COMMIT_MESSAGE=$COMMIT_MESSAGE, REPOSITORY_BASE=$REPOSITORY_BASENAME" echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2 if [[ "$workFlowResult" == *"PRE_RECEIVE_OK"* ]]; then echo " >>>>>>>>>>>>>>>>> $workFlowResult >>>>>>>>>>>>>>>>>" >&2 COMMIT_CHECK_STATUS=0 fi fi } check_all_commits() { REVISIONS=$(git rev-list $OLD_REVISION..$NEW_REVISION) IFS='\n' read -ra LIST_OF_REVISIONS <<< "$REVISIONS" if [ $(git rev-parse --is-bare-repository) = true ] then REPOSITORY_BASENAME=$(basename "$PWD") else REPOSITORY_BASENAME=$(basename $(readlink -nf "$PWD"/..)) fi echo REPOSITORY_BASENAME is $REPOSITORY_BASENAME REPOSITORY_BASENAME=$(basename "$PWD") REPOSITORY_BASENAME=${REPOSITORY_BASENAME%.git} for rid in "${!LIST_OF_REVISIONS[@]}"; do REVISION=${LIST_OF_REVISIONS[rid]} COMMIT_MESSAGE=$(git cat-file commit $REVISION | sed '1,/^$/d') COMMIT_AUTHOR=$(git cat-file commit $REVISION | grep committer | sed 's/^.* \([^@ ]\+@[^ ]\+\) \?.*$/\1/' | sed 's/<//' | sed 's/>//' | sed 's/@$COMPANY_EMAIL//') check_single_commit if [ "$COMMIT_CHECK_STATUS" != "0" ]; then echo "Commit validation failed for commit $REVISION" >&2 exit 1 fi done } # Get custom commit message format while read OLD_REVISION NEW_REVISION REFNAME ; do check_all_commits done exit 0 

3) Aunque no formaba parte de la pregunta, la integración de las comprobaciones de PMD en el server sin el uso de plugins de PMD Jenkins requería la descarga de las dependencies de inicio ejecutables de PMD, la ejecución de PMD desde una secuencia de commands python para analizar de forma estática los files fuente siendo empujado por los desarrolladores al server git (server Gitlab). La secuencia de commands python que inicia PMD podría integrarse fácilmente en el script bash shell anterior. El script de python, que es una adaptación de http://bluec0re.blogspot.com.ng/2012/05/git-pre-receive-hook-with-checkstyle.html , se puede encontrar a continuación:

 #!/usr/bin/env python import subprocess import sys import tempfile import shutil import os import errno # variables for checkstyle #checkstyle = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/checkstyle-7.5.1-all.jar' #checkstyle_config = '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/sun_checks.xml' pmd = '/home/gitlab/gitlab_custom_hooks/pmd-bin-5.5.4/bin/run.sh' # implementing check_output for python < 2.7 if not hasattr(subprocess, 'check_output'): def check_output(*popenargs, **kwargs): if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] er = subprocess.CalledProcessError(retcode, cmd) er.output = output raise er return output subprocess.check_output = check_output # helper for calling executables def call(*args, **kwargs): return subprocess.check_output(*args, **kwargs).strip() # helper for calling git def call_git(cmd, *args, **kwargs): return call(['git'] + cmd, *args, **kwargs) # get all new commits from stdin def get_commits(): commits = {} for line in sys.stdin: old, new, ref = line.strip().split(' ') if old == '0000000000000000000000000000000000000000': old = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' if ref not in commits: commits[ref] = [] commits[ref].append({ 'old': old, 'new': new, 'files': get_changed_files(old, new) }) return commits # get a list of changed files between to commits def get_changed_files(old, new): return call_git(['diff', '--name-only', old, new]).split('\n') # get filemode, object type (blob,tree,commit), hash for the given file at the # given commit def get_change_type(commit, filename): return call_git(['ls-tree', commit, filename]).split('\t')[0].split(' ') commits = get_commits() # use the latest file commit only print "Cleaning up file list..." files = {} count = 0 for ref, data in commits.iteritems(): files[ref] = {} for commit in data: for filename in commit['files']: if not filename.lower().endswith('.java'): continue files[ref][filename] = get_change_type(commit['new'], filename) count += len(files[ref]) print "%d Files to check in %d branches" % (count, len(files)) # create temporary dir and save a copy of the new files tempdir = tempfile.mkdtemp('git_hook') for ref, files in files.iteritems(): for filename, data in files.iteritems(): dname = os.path.dirname(filename) bname = os.path.basename(filename) try: os.makedirs(os.path.join(tempdir, dname)) except OSError, exc: if exc.errno == errno.EEXIST: # directory exists already pass else: raise with open(os.path.join(tempdir, dname, bname), 'w') as fp: fp.write(call_git(['cat-file', data[1], data[2]])) try: # call checkstyle and/or pmd and print output # print call(['java', '-jar', checkstyle, '-c', checkstyle_config, tempdir]) # print call(['java', '-jar', '/var/opt/gitlab/git-data/repositories/product-common/ProductCommonParent.git/custom_hooks/hooks-0.0.1-SNAPSHOT.jar', '-prercv', '79', 'developer-email-id', "I am now done with issue #500 #501 #502"]) print call([pmd, 'pmd', '-d', tempdir, '-f', 'text', '-R', 'rulesets/java/basic.xml,rulesets/java/unusedcode.xml,rulesets/java/imports.xml,rulesets/java/strings.xml,rulesets/java/braces.xml,rulesets/java/clone.xml,rulesets/java/design.xml,rulesets/java/clone.xml,rulesets/java/finalizers.xml,rulesets/java/junit.xml,rulesets/java/migrating.xml,rulesets/java/optimizations.xml,rulesets/java/strictexception.xml,rulesets/java/sunsecure.xml,rulesets/java/typeresolution.xml']) print "SUCCESS" except subprocess.CalledProcessError, ex: print ex.output # print checkstyle and/or pmd messages exit(1) finally: # remove temporary directory shutil.rmtree(tempdir)