├── .travis.yml ├── composer.json ├── icingaexchange.yml ├── LICENSE ├── .gitignore ├── bin ├── check_git_log └── check_git └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: sh 2 | 3 | before_script: 4 | - sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ trusty-backports restricted main universe" 5 | - sudo apt-get update -qq 6 | - sudo apt-get install -qq shellcheck 7 | 8 | script: 9 | - shellcheck --shell=sh -e SC2034 bin/check_git 10 | - shellcheck --shell=sh bin/check_git_log 11 | 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cytopia/check_git", 3 | "description": "Nagios plugin to verify a git repository.", 4 | "type": "library", 5 | "keywords": ["nagios", "nagios-plugin", "icinga", "git"], 6 | "homepage": "https://github.com/cytopia/check_git", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name" : "cytopia", 11 | "homepage": "https://github.com/cytopia", 12 | "role": "Developer" 13 | } 14 | ], 15 | "bin": ["bin/check_git"] 16 | } 17 | -------------------------------------------------------------------------------- /icingaexchange.yml: -------------------------------------------------------------------------------- 1 | name: check_git 2 | description: "file:///README.md" 3 | url: "https://github.com/cytopia/check_git" 4 | tags: git 5 | vendor: cytopia 6 | target: Operating System,Website 7 | type: Plugin 8 | license: MIT 9 | releases: 10 | - 11 | name: 0.6 12 | description: "0.6 Release" 13 | files: 14 | - 15 | name: check_git 16 | url: "file:///bin/check_git" 17 | description: "Directly check a git directory." 18 | checksum: 3e1ec28d5f7c2b44d8ddfb5429e8e4e4 19 | - 20 | name: check_git_log 21 | url: "file:///bin/check_git_log" 22 | description: "Check the logfile created by check_git" 23 | checksum: eb1476d31acce38892e58120d53ee5fd 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 cytopia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Note: 2 | # To effectively apply the changes you will have 3 | # to re-index the git index (if there are already 4 | # commited files) 5 | # 6 | # $ git rm -r --cached . 7 | # $ git add . 8 | # $ git commit -m ".gitignore index rebuild" 9 | # 10 | 11 | 12 | ###################################### 13 | # CUSTOM 14 | ###################################### 15 | 16 | 17 | 18 | ###################################### 19 | # GENERIC 20 | ###################################### 21 | 22 | ###### std ###### 23 | .lock 24 | *.log 25 | 26 | ###### patches/diffs ###### 27 | *.patch 28 | *.diff 29 | *.orig 30 | *.rej 31 | 32 | 33 | ###################################### 34 | # Operating Systems 35 | ###################################### 36 | 37 | ###### OSX ###### 38 | ._* 39 | .DS* 40 | .Spotlight-V100 41 | .Trashes 42 | 43 | ###### Windows ###### 44 | Thumbs.db 45 | ehthumbs.db 46 | Desktop.ini 47 | $RECYCLE.BIN/ 48 | *.lnk 49 | *.shortcut 50 | 51 | ###################################### 52 | # Editors 53 | ###################################### 54 | 55 | ###### Sublime ###### 56 | *.sublime-workspace 57 | *.sublime-project 58 | 59 | ###### Eclipse ###### 60 | .classpath 61 | .buildpath 62 | .project 63 | .settings/ 64 | 65 | ###### Netbeans ###### 66 | /nbproject/ 67 | 68 | ###### Intellij IDE ###### 69 | .idea/ 70 | .idea_modules/ 71 | 72 | ###### vim ###### 73 | *.swp 74 | *.swo 75 | *~ 76 | 77 | ###### TextMate ###### 78 | .tm_properties 79 | *.tmproj 80 | 81 | ###### BBEdit ###### 82 | *.bbprojectd 83 | *.bbproject 84 | 85 | -------------------------------------------------------------------------------- /bin/check_git_log: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Nagios plugin to monitor the state of git repos. 4 | # 5 | 6 | ################################################################################ 7 | # 8 | # V A R I A B L E S 9 | # 10 | ################################################################################ 11 | 12 | # Some creds 13 | INFO_NAME="check_git_log" 14 | INFO_AUTHOR="cytopia " 15 | INFO_GPGKEY="0x695128A2" 16 | INFO_LICENSE="MIT" 17 | INFO_GITHUB="https://github.com/cytopia/check_git" 18 | INFO_DATE="2016-12-11" 19 | INFO_VERSION="0.6" 20 | 21 | # Get the path 22 | export PATH="$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" 23 | 24 | # Nagios error codes 25 | EXIT_OK=0 26 | #EXIT_WARN=1 27 | #EXIT_ERR=2 28 | EXIT_UNKNOWN=3 29 | 30 | 31 | 32 | ################################################################################ 33 | # 34 | # F U N C T I O N S 35 | # 36 | ################################################################################ 37 | 38 | # Test if argument is an integer 39 | # @return integer 0: is numer | 1 not a number 40 | # 41 | # usage: 42 | # if ! isint ${DELETE_IF_OLDER} > /dev/null 2>&1 ; then 43 | # echo "not an integer" 44 | # fi 45 | isint(){ 46 | printf '%d' "$1" >/dev/null 2>&1 && return 0 || return 1; 47 | } 48 | 49 | # Give some creds 50 | # @output string The creds. 51 | # @return integer 0 52 | print_version() { 53 | printf "Name: %s\n" "${INFO_NAME}" 54 | printf "Version: %s (%s)\n" "${INFO_VERSION}" "${INFO_DATE}" 55 | printf "Author: %s (%s)\n" "${INFO_AUTHOR}" "${INFO_GPGKEY}" 56 | printf "Github: %s\n" "${INFO_GITHUB}" 57 | printf "License: %s\n" "${INFO_LICENSE}" 58 | return 0 59 | } 60 | 61 | 62 | # Usage 63 | # @output string The usage screen. 64 | # @return integer 0 65 | print_usage() { 66 | printf "Usage: %s -f \n" "${INFO_NAME}" 67 | printf "OR %s --help\n" "${INFO_NAME}" 68 | printf "OR %s --version\n\n" "${INFO_NAME}" 69 | return 0 70 | } 71 | 72 | # Help 73 | # @output string The help screen. 74 | # @return integer 0 75 | print_help() { 76 | 77 | # Show usage first 78 | print_usage 79 | 80 | # Show help and details 81 | printf "Nagios plugin that will parse the logfile created by 'check_git'.\n\n" 82 | 83 | printf " -f The full path to logfile created by 'check_git'\n\n" 84 | 85 | printf " --help Show this help\n" 86 | printf " --version Show version information.\n" 87 | return 0 88 | } 89 | 90 | 91 | 92 | ################################################################################ 93 | # 94 | # E N T R Y P O I N T 95 | # 96 | ################################################################################ 97 | 98 | ############################################################ 99 | # Check for --help or --version arguments 100 | ############################################################ 101 | if [ "${1}" = "--help" ]; then 102 | print_help 103 | exit $EXIT_OK 104 | fi 105 | if [ "${1}" = "--version" ]; then 106 | print_version 107 | exit $EXIT_OK 108 | fi 109 | 110 | 111 | ############################################################ 112 | # Retrieve arguments 113 | ############################################################ 114 | while test -n "$1"; do 115 | case "$1" in 116 | # ---- 1. Logfile 117 | -f) 118 | # Get next arg in list (Path) 119 | shift 120 | LOGFILE="$1" 121 | ;; 122 | *) 123 | printf "Unknown argument: %s\n" "$1" 124 | print_usage 125 | exit $EXIT_UNKNOWN 126 | ;; 127 | esac 128 | shift 129 | done 130 | 131 | 132 | ############################################################ 133 | # Validate arguments 134 | ############################################################ 135 | 136 | # -f is mandatory!!! 137 | if [ -z "$LOGFILE" ]; then 138 | printf "[UNKNOWN] Logfile parameter '-f' not specified.\n" 139 | print_usage 140 | exit $EXIT_UNKNOWN 141 | fi 142 | 143 | # Check if logfile exists 144 | if [ ! -f "$LOGFILE" ]; then 145 | echo "[UNKNOWN] Logfile does not exist: ${LOGFILE}." 146 | exit $EXIT_UNKNOWN 147 | fi 148 | 149 | # Check if logfile is readable 150 | if [ ! -r "$LOGFILE" ]; then 151 | echo "[UNKNOWN] Logfile is not readable: ${LOGFILE}." 152 | exit $EXIT_UNKNOWN 153 | fi 154 | 155 | # Check if last line contains error code 156 | NAGIOS_EXIT="$(tail -n1 "$LOGFILE")" 157 | if ! isint "${NAGIOS_EXIT}" > /dev/null 2>&1 ; then 158 | echo "[UNKNOWN] Logfile seems invalid: Does not contain exit code on last line." 159 | exit $EXIT_UNKNOWN 160 | fi 161 | 162 | # Check if exit code is within bounds 163 | if [ "$NAGIOS_EXIT" != "0" ] && [ "$NAGIOS_EXIT" != "1" ] && [ "$NAGIOS_EXIT" != "2" ] && [ "$NAGIOS_EXIT" != "3" ]; then 164 | echo "[UNKNOWN] Exit code not within bounds in $LOGFILE" 165 | exit $EXIT_UNKNOWN 166 | fi 167 | 168 | 169 | 170 | NUM_LINES="$(wc -l < "$LOGFILE")" 171 | NUM_LINES="$((NUM_LINES-1))" # Last line is exit code 172 | 173 | OUTPUT="$(head -n${NUM_LINES} "$LOGFILE")" 174 | 175 | echo "$OUTPUT" 176 | exit "$NAGIOS_EXIT" 177 | 178 | # " vim: set ts=4: 179 | 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # check_git 2 | 3 | Nagios plugin to verify a git directory. Checks include `git status` (with or without submodules), HEAD is on a branch or tag (any or specified), has diffs against remote (with or without submodules), HEAD (tag, branch or commit) is GPG signed and the signature is valid. 4 | 5 | [![Build Status](https://travis-ci.org/cytopia/check_git.svg?branch=master)](https://travis-ci.org/cytopia/check_git) 6 | [![Latest Stable Version](https://poser.pugx.org/cytopia/check_git/v/stable)](https://packagist.org/packages/cytopia/check_git) [![Total Downloads](https://poser.pugx.org/cytopia/check_git/downloads)](https://packagist.org/packages/cytopia/check_git) [![Latest Unstable Version](https://poser.pugx.org/cytopia/check_git/v/unstable)](https://packagist.org/packages/cytopia/check_git) [![License](https://poser.pugx.org/cytopia/check_git/license)](http://opensource.org/licenses/MIT) 7 | [![POSIX](https://img.shields.io/badge/posix-100%25-brightgreen.svg)](https://en.wikipedia.org/?title=POSIX) 8 | [![Type](https://img.shields.io/badge/type-%2Fbin%2Fsh-red.svg)](https://en.wikipedia.org/?title=Bourne_shell) 9 | 10 | --- 11 | 12 | | [![Awesome-Nagios-Plugins](https://raw.githubusercontent.com/cytopia/awesome-nagios-plugins/master/doc/img/awesome-nagios.png)](https://github.com/cytopia/awesome-nagios-plugins) | Find more plugins at [Awesome Nagios](https://github.com/cytopia/awesome-nagios-plugins) | 13 | |---|---| 14 | | [![Icinga Exchange](https://raw.githubusercontent.com/cytopia/awesome-nagios-plugins/master/doc/img/icinga.png)](https://exchange.icinga.com/cytopia) | **Find more plugins at [Icinga Exchange](https://exchange.icinga.com/cytopia)** | 15 | | [![Nagios Exchange](https://raw.githubusercontent.com/cytopia/awesome-nagios-plugins/master/doc/img/nagios.png)](https://exchange.nagios.org/directory/Owner/cytopia/1) | **Find more plugins at [Nagios Exchange](https://exchange.nagios.org/directory/Owner/cytopia/1)** | 16 | 17 | --- 18 | 19 | ## 1. Nagios / Icinga integration 20 | 21 | There are two ways how to integrate this with nagios or icinga. 22 | 23 | ### 1.1 Direct approach (not recommended) 24 | 25 | Use `check_git` directly from nagios and test a repository every time a check is triggered. In order for the output to be usaeble by Nagios / Icinga, you will have to use `-n `, which will amongst others remove the shell colors from the output. Correct exit codes for `success`, `warning`, `error` and `unknown` are thrown by default. 26 | 27 | **Example** 28 | 29 | ```shell 30 | $ check_git -d -s -n 31 | ``` 32 | 33 | ### 1.2 Approach to just parse a logfile (recommended) 34 | This approach reduces the overhead to check a repository every 5min by nagios, and rather check the logfile created by `check_git`. 35 | Sometimes a full check (status, gpg, remote check, etc) can take up to a few seconds and u don't want to stress the system every 5min. 36 | 37 | For that to work, you simply add `check_git` to your crontab and only check your repository every 4 hours for example. 38 | Nagios / Icinga can still check the logfile every 5 minutes. 39 | 40 | **Crontab** 41 | ```shell 42 | 0 0,4,8,12,16,20 * * * /full/path/to/check_git -d -n -l /var/log/git/.log 43 | ``` 44 | This will update the logfile under `/var/log/git/.log` every 4 hours and nagios/icinga can however check on it as often as they want via: 45 | 46 | **The actual check** 47 | ```shell 48 | $ check_git_log -f /var/log/git/.log 49 | ``` 50 | 51 | 52 | ## 2. Examples 53 | 54 | The following examples show each options separately, you can of course combine most checks. 55 | 56 | ### 2.1 Check git status 57 | 58 | Without submodules (`-s`) 59 | ```shell 60 | $ check_git -d /shared/httpd/my-project/ -s -n My-Project 61 | [SUCCESS] My-Project git repo is healthy. 62 | [SUCCESS] Git status: clean 63 | ``` 64 | With submodules (`-S`) 65 | ```shell 66 | $ check_git -d /shared/httpd/my-project/ -S -n My-Project 67 | [CRITICAL] My-Project git repo has errors. 68 | [SUCCESS] Git status: clean 69 | [CRITICAL] Git status: submodule(s) unclean 70 | ``` 71 | 72 | 73 | ### 2.2 Check status of HEAD (branch or tag) 74 | 75 | HEAD must be on a branch (any branch `-b`) 76 | ```shell 77 | $ check_git -d /shared/httpd/my-project/ -b -n My-Project 78 | [SUCCESS] My-Project git repo is healthy. 79 | [SUCCESS] Git Branch: on branch 'develop' 80 | ``` 81 | 82 | HEAD must be on branch *develop* (`-B`) 83 | ```shell 84 | $ check_git -d /shared/httpd/my-project/ -B develop -n My-Project 85 | [SUCCESS] My-Project git repo is healthy. 86 | [SUCCESS] Git Branch: on branch 'develop' 87 | ``` 88 | 89 | HEAD must be on a tag (any tag `-t`) 90 | ```shell 91 | $ check_git -d /shared/httpd/my-project/ -t -n My-Project 92 | [CRITICAL] My-Project git repo has errors. 93 | [CRITICAL] Git Tag: not on any tag 94 | ``` 95 | 96 | HEAD must be on tag 0.3 (`-T`) 97 | ```shell 98 | $ check_git -d /shared/httpd/my-project/ -T 0.3 -n My-Project 99 | [CRITICAL] My-Project git repo has errors. 100 | [CRITICAL] Git Tag: on tag '0.2', but should be on: '0.3' 101 | ``` 102 | 103 | ### 2.3 Check GPG signature of commit/tag 104 | Check if the current commit (or if HEAD is a tag, the current tag) is signed with gpg and valid (`-g`). 105 | ```shell 106 | $ check_git -d /shared/httpd/my-project/ -g -n My-Project 107 | [SUCCESS] My-Project git repo is healthy. 108 | [SUCCESS] GPG Signed: Yes with key: 695128A2 109 | [SUCCESS] GPG Pubkey: available 110 | [SUCCESS] GPG Signer: cytopia 111 | [SUCCESS] GPG Trust: ULTIMATE 112 | ``` 113 | 114 | Check if the current commit (or if HEAD is a tag, the current tag) is signed with gpg, valid and matches one of the specified key ids (`-G`). 115 | ```shell 116 | $ check_git -d /shared/httpd/my-project/ -G 695128A2,00000000,11111111,22222222 -n My-Project 117 | [SUCCESS] My-Project git repo is healthy. 118 | [SUCCESS] GPG Signed: Yes with expected key: 695128A2 119 | [SUCCESS] GPG Pubkey: available 120 | [SUCCESS] GPG Signer: cytopia 121 | [SUCCESS] GPG Trust: ULTIMATE 122 | ``` 123 | 124 | ### 2.4 Check Diff against remote 125 | 126 | Check if you have new code to pull (only makes sense if you are on a branch). Check excluding submodules (`-R`) 127 | ```shell 128 | $ check_git -d /shared/httpd/my-project/ -R origin -n My-Project 129 | [SUCCESS] My-Project git repo is healthy. 130 | [SUCCESS] Git remote: equals with 'origin' 131 | ``` 132 | 133 | Check if you have new code to pull (only makes sense if you are on a branch). Check including submodules (`-r`) 134 | ```shell 135 | $ check_git -d /shared/httpd/my-project/ -r origin -n My-Project 136 | [CRITICAL] My-Project git repo has errors. 137 | [SUCCESS] Git remote: equals with 'origin' 138 | [CRITICAL] Git remote: submodule(s) differs from 'origin' 139 | ``` 140 | 141 | ### 2.5 Show additional verbose output 142 | 143 | Show some verbose output (will also be visible within nagios extended output) 144 | ```shell 145 | $ check_git -d /shared/httpd/my-project/ -v -n My-Project 146 | [SUCCESS] My-Project git repo is healthy. 147 | [INFO] Bin: git version 2.10.2 148 | [INFO] Path: /shared/httpd/my-project/ 149 | [INFO] Submodules: 2 150 | [INFO] HEAD: on branch 'develop' 151 | [INFO] GPG: signed 695128A2 152 | ``` 153 | 154 | 155 | ## 3. Usage 156 | 157 | ```shell 158 | Usage: check_git -d [-s|-S] [-r|-R ] [-b] [-B ] [-t] [-T ]] [-g] [-G [,]] [-v] 159 | OR -d [-n ] [-l logfile] [-s|-S] [-r|-R ] [-b] [-B ] [-t] [-T ]] [-g] [-G [,]] [-v] 160 | OR -h 161 | OR -V 162 | 163 | check_git can validate a git repository by different requirements 164 | You can have normal output or nagios (-n) compatible output to 165 | integrate this into your monitoring system. 166 | 167 | Required arguments: 168 | 169 | -d Specify path to git repository. 170 | 171 | Optional arguments (output): 172 | 173 | -v Be more verbose. 174 | 175 | -n Create nagios style check outout. 176 | Removes colors and adds a project name to the first line. 177 | 178 | -l Log to file instead of stdout. 179 | This is especially useful if you want to integrate this check via nagios. 180 | You can then add a cronjob which periodically logs to file (as your deploy user) 181 | and the nagios check simply parses the logfile via 'check_git_log'. 182 | Requires '-n'. 183 | 184 | Optional arguments (checks): 185 | 186 | -s Check if git directory is clean. 187 | This also take any submodules into account. 188 | To prevent checking submodules use '-S'. 189 | '-s' and '-S' are mutually exclusive. 190 | 191 | -S Check if git directory is clean (ignore submodules). 192 | This ignores any submodules. 193 | To also check against submodules use '-s'. 194 | '-s' and '-S' are mutually exclusive. 195 | 196 | 197 | -r Check if git repository is in sync with remote. 198 | This option makes only sense, if your repository is 199 | checked out on a branch that can be compared with remote. 200 | This also take any submodules into account. 201 | To prevent checking submodules use '-R'. 202 | '-r' and '-R' are mutually exclusive. 203 | 204 | -R Check if git repository is in sync with remote (ignore submodules). 205 | This option makes only sense, if your repository is 206 | checked out on a branch that can be compared with remote. 207 | This ignores any submodules. 208 | To also check submodules use '-r'. 209 | '-r' and '-R' are mutually exclusive. 210 | -b Check if repository is checkout out on a branch. 211 | No detached HEAD or tag. 212 | '-b', '-B', '-t' and '-T' are mutually exclusive. 213 | 214 | -B [] Check if repository is checkout out on the specified branch. 215 | '-b', '-B', '-t' and '-T' are mutually exclusive. 216 | 217 | -t Check if repository is checkout out on a tag. 218 | No detached HEAD or branch. 219 | '-b', '-B', '-t' and '-T' are mutually exclusive. 220 | 221 | -T [] Check if repository is checkout out on the specified tag. 222 | No detached HEAD or branch. 223 | '-b', '-B', '-t' and '-T' are mutually exclusive. 224 | 225 | -g Check if current HEAD is signed and has a valid GPG signature. 226 | If current HEAD is a tag, the GPG signature of the tag is checked, 227 | instead of the signature of the current commit. 228 | For this to pass, you will also have to add your trusted 229 | GPG public keys locally. 230 | Returns: 231 | Error, if not signed. 232 | Warning, if signed, but pubkey is not available. 233 | Warning, if signed, but pubkey is not trusted. 234 | OK, if signed, pubkey is available and trusted. 235 | '-g', and '-G' are mutually exclusive. 236 | 237 | -G [,] Check if current HEAD is signed by the given key id (hash) and has a valid GPG signature. 238 | You can separate multiple key-id's via comma without space. 239 | key-id (hash) must be the last 8 characters (all uppercase) of the key. 240 | If current HEAD is a tag, the GPG signature of the tag is checked, 241 | instead of the signature of the current commit. 242 | For this to pass, you will also have to add your trusted 243 | GPG public keys locally. 244 | Returns: 245 | Error, if not signed. 246 | Error, if signed with a different key-id. 247 | Warning, if signed, but pubkey is not available. 248 | Warning, if signed, but pubkey is not trusted. 249 | OK, if signed, pubkey is available and trusted. 250 | '-g', and '-G' are mutually exclusive. 251 | 252 | Version and Help: 253 | 254 | -V Show version information 255 | -h Show this help screen 256 | ``` 257 | -------------------------------------------------------------------------------- /bin/check_git: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | 4 | 5 | #################################################################################################### 6 | #################################################################################################### 7 | ### 8 | ### G L O B A L V A R I A B L E S 9 | ### 10 | #################################################################################################### 11 | #################################################################################################### 12 | 13 | 14 | ################################################################################ 15 | # Defined variables 16 | ################################################################################ 17 | 18 | ### 19 | ### Credits 20 | ### 21 | INFO_NAME="check_git" 22 | INFO_AUTHOR="cytopia " 23 | INFO_GPGKEY="0x695128A2" 24 | INFO_LICENSE="MIT" 25 | INFO_GITHUB="https://github.com/cytopia/check_git" 26 | INFO_DATE="2016-12-11" 27 | INFO_VERSION="0.6" 28 | 29 | 30 | ### 31 | ### Console colors (for logging) 32 | ### 33 | # If having a tty (console) use colors 34 | if [ -t 1 ]; then 35 | CLR_SUCC="\033[0;32m" # Green 36 | CLR_WARN="\033[0;33m" # Yellow 37 | CLR_CRIT="\033[0;31m" # Red 38 | CLR_UNKN="\033[0;35m" # Magenta 39 | CLR_INFO="\033[0;34m" # Blue 40 | CLR_RST="\033[m" # Normal 41 | # Dump terminal (e.g.: via cron) 42 | else 43 | CLR_SUCC= 44 | CLR_WARN= 45 | CLR_CRIT= 46 | CLR_UNKN= 47 | CLR_INFO= 48 | CLR_RST= 49 | fi 50 | 51 | 52 | ### 53 | ### Nagios exit codes 54 | ### 55 | EXIT_SUCC=0 56 | EXIT_WARN=1 57 | EXIT_CRIT=2 58 | EXIT_UNKN=3 59 | 60 | 61 | ### 62 | ### Final output and exit code 63 | ### 64 | PROGRAM_EXIT_TEXT="" 65 | PROGRAM_EXIT_CODE=0 66 | 67 | 68 | 69 | ################################################################################ 70 | # Variables populated via command line args 71 | ################################################################################ 72 | 73 | ### 74 | ### 1/3 Mandatory arguments 75 | ### 76 | 77 | # @var string Repository path 78 | GIT_REPOSITORY_PATH= 79 | 80 | 81 | ### 82 | ### 2/3 Optional output arguments 83 | ### 84 | 85 | #@boolean Output verbose 86 | OUTPUT_VERBOSE=0 87 | # @boolean Output in nagios form? 88 | OUTPUT_NAGIOS=0 89 | OUTPUT_NAGIOS_NAME= 90 | #@boolean Output into logfile? 91 | OUTPUT_LOGFILE=0 92 | OUTPUT_LOGFILE_NAME= 93 | 94 | 95 | ### 96 | ### 3/3 Optional check arguments 97 | ### 98 | 99 | # @var boolean Check git status? 100 | CHECK_STATUS_WITH_SUB=0 101 | CHECK_STATUS_WITHOUT_SUB=0 102 | 103 | # @var boolean Diff against remote? 104 | CHECK_REMOTE_WITH_SUB=0 105 | CHECK_REMOTE_WITHOUT_SUB=0 106 | # @var string Remote name 107 | CHECK_REMOTE_NAME= 108 | 109 | # @var boolean Check if on a branch 110 | CHECK_BRANCH_ANY=0 111 | CHECK_BRANCH=0 112 | # @var string Check if on this given branch 113 | CHECK_BRANCH_NAME= 114 | 115 | # @var boolean Check if on a tag 116 | CHECK_TAG_ANY=0 117 | CHECK_TAG=0 118 | # @var string Check if on this given tag 119 | CHECK_TAG_NAME= 120 | 121 | # @var boolean Check If current commit has valid GPG signature 122 | CHECK_GPG_ANY=0 123 | CHECK_GPG=0 124 | # @var string Check if signed with this keyid's (one or comma separated) 125 | CHECK_GPG_KEYS= 126 | 127 | 128 | 129 | 130 | #################################################################################################### 131 | #################################################################################################### 132 | ### 133 | ### G L O B A L F U N C T I O N S 134 | ### 135 | #################################################################################################### 136 | #################################################################################################### 137 | 138 | 139 | ################################################################################ 140 | # Program functions 141 | ################################################################################ 142 | 143 | print_usage() { 144 | 145 | echo "Usage: check_git -d [-s|-S] [-r|-R ] [-b] [-B ] [-t] [-T ]] [-g] [-G [,]] [-v]" 146 | echo "OR -d [-n ] [-l logfile] [-s|-S] [-r|-R ] [-b] [-B ] [-t] [-T ]] [-g] [-G [,]] [-v]" 147 | echo "OR -h" 148 | echo "OR -V" 149 | 150 | echo 151 | echo "check_git can validate a git repository by different requirements" 152 | echo "You can have normal output or nagios (-n) compatible output to" 153 | echo "integrate this into your monitoring system." 154 | echo 155 | 156 | echo " Required arguments:" 157 | echo 158 | 159 | echo " -d Specify path to git repository." 160 | echo 161 | 162 | 163 | echo " Optional arguments (output):" 164 | echo 165 | 166 | echo " -v Be more verbose." 167 | echo 168 | echo " -n Create nagios style check outout." 169 | echo " Removes colors and adds a project name to the first line." 170 | echo 171 | 172 | echo " -l Log to file instead of stdout." 173 | echo " This is especially useful if you want to integrate this check via nagios." 174 | echo " You can then add a cronjob which periodically logs to file (as your deploy user)" 175 | echo " and the nagios check simply parses the logfile via 'check_git_log'." 176 | echo " Requires '-n'." 177 | echo 178 | 179 | echo " Optional arguments (checks):" 180 | echo 181 | 182 | echo " -s Check if git directory is clean." 183 | echo " This also take any submodules into account." 184 | echo " To prevent checking submodules use '-S'." 185 | echo " '-s' and '-S' are mutually exclusive." 186 | echo 187 | 188 | echo " -S Check if git directory is clean (ignore submodules)". 189 | echo " This ignores any submodules." 190 | echo " To also check against submodules use '-s'." 191 | echo " '-s' and '-S' are mutually exclusive." 192 | echo 193 | echo 194 | 195 | echo " -r Check if git repository is in sync with remote." 196 | echo " This option makes only sense, if your repository is" 197 | echo " checked out on a branch that can be compared with remote." 198 | echo " This also take any submodules into account." 199 | echo " To prevent checking submodules use '-R'." 200 | echo " '-r' and '-R' are mutually exclusive." 201 | echo 202 | 203 | echo " -R Check if git repository is in sync with remote (ignore submodules)." 204 | echo " This option makes only sense, if your repository is" 205 | echo " checked out on a branch that can be compared with remote." 206 | echo " This ignores any submodules." 207 | echo " To also check submodules use '-r'." 208 | echo " '-r' and '-R' are mutually exclusive." 209 | 210 | echo " -b Check if repository is checkout out on a branch." 211 | echo " No detached HEAD or tag." 212 | echo " '-b', '-B', '-t' and '-T' are mutually exclusive." 213 | echo 214 | 215 | echo " -B [] Check if repository is checkout out on the specified branch." 216 | echo " '-b', '-B', '-t' and '-T' are mutually exclusive." 217 | echo 218 | 219 | echo " -t Check if repository is checkout out on a tag." 220 | echo " No detached HEAD or branch." 221 | echo " '-b', '-B', '-t' and '-T' are mutually exclusive." 222 | echo 223 | 224 | echo " -T [] Check if repository is checkout out on the specified tag." 225 | echo " No detached HEAD or branch." 226 | echo " '-b', '-B', '-t' and '-T' are mutually exclusive." 227 | echo 228 | 229 | echo " -g Check if current HEAD is signed and has a valid GPG signature." 230 | echo " If current HEAD is a tag, the GPG signature of the tag is checked," 231 | echo " instead of the signature of the current commit." 232 | echo " For this to pass, you will also have to add your trusted" 233 | echo " GPG public keys locally." 234 | echo " Returns:" 235 | echo " Error, if not signed." 236 | echo " Warning, if signed, but pubkey is not available." 237 | echo " Warning, if signed, but pubkey is not trusted." 238 | echo " OK, if signed, pubkey is available and trusted." 239 | echo " '-g', and '-G' are mutually exclusive." 240 | echo 241 | 242 | echo " -G [,] Check if current HEAD is signed by the given key id (hash) and has a valid GPG signature." 243 | echo " You can separate multiple key-id's via comma without space." 244 | echo " key-id (hash) must be the last 8 characters (all uppercase) of the key." 245 | echo " If current HEAD is a tag, the GPG signature of the tag is checked," 246 | echo " instead of the signature of the current commit." 247 | echo " For this to pass, you will also have to add your trusted" 248 | echo " GPG public keys locally." 249 | echo " Returns:" 250 | echo " Error, if not signed." 251 | echo " Error, if signed with a different key-id." 252 | echo " Warning, if signed, but pubkey is not available." 253 | echo " Warning, if signed, but pubkey is not trusted." 254 | echo " OK, if signed, pubkey is available and trusted." 255 | echo " '-g', and '-G' are mutually exclusive." 256 | echo 257 | 258 | echo " Version and Help:" 259 | echo 260 | 261 | echo " -V Show version information" 262 | echo " -h Show this help screen" 263 | 264 | } 265 | 266 | # Give some creds 267 | # @output string The creds. 268 | # @return integer 0 269 | print_version() { 270 | printf "Name: %s\n" "${INFO_NAME}" 271 | printf "Version: %s (%s)\n" "${INFO_VERSION}" "${INFO_DATE}" 272 | printf "Author: %s (%s)\n" "${INFO_AUTHOR}" "${INFO_GPGKEY}" 273 | printf "Github: %s\n" "${INFO_GITHUB}" 274 | printf "License: %s\n" "${INFO_LICENSE}" 275 | return 0 276 | } 277 | 278 | 279 | #log "succ" "" 280 | #log "warn" 281 | #log "crit" 282 | #log "info" 283 | log() { 284 | _lvl="${1}" 285 | _msg="${2}" 286 | 287 | 288 | if [ "${_lvl}" = "succ" ]; then 289 | PROGRAM_EXIT_TEXT="$( merge_text "${PROGRAM_EXIT_TEXT}" "${CLR_SUCC}[SUCCESS]${CLR_RST} ${_msg}" )" 290 | PROGRAM_EXIT_CODE="$( merge_exit_codes "${PROGRAM_EXIT_CODE}" "${EXIT_SUCC}" )" 291 | elif [ "${_lvl}" = "warn" ]; then 292 | PROGRAM_EXIT_TEXT="$( merge_text "${PROGRAM_EXIT_TEXT}" "${CLR_WARN}[WARNING]${CLR_RST} ${_msg}" )" 293 | PROGRAM_EXIT_CODE="$( merge_exit_codes "${PROGRAM_EXIT_CODE}" "${EXIT_WARN}" )" 294 | elif [ "${_lvl}" = "crit" ]; then 295 | PROGRAM_EXIT_TEXT="$( merge_text "${PROGRAM_EXIT_TEXT}" "${CLR_CRIT}[CRITICAL]${CLR_RST} ${_msg}" )" 296 | PROGRAM_EXIT_CODE="$( merge_exit_codes "${PROGRAM_EXIT_CODE}" "${EXIT_CRIT}" )" 297 | elif [ "${_lvl}" = "unkn" ]; then 298 | PROGRAM_EXIT_TEXT="$( merge_text "${PROGRAM_EXIT_TEXT}" "${CLR_CRIT}[UNKNOWN]${CLR_RST} ${_msg}" )" 299 | PROGRAM_EXIT_CODE="$( merge_exit_codes "${PROGRAM_EXIT_CODE}" "${EXIT_UNKN}" )" 300 | elif [ "${_lvl}" = "info" ]; then 301 | if [ "${OUTPUT_VERBOSE}" = "1" ]; then 302 | PROGRAM_EXIT_TEXT="$( merge_text "${PROGRAM_EXIT_TEXT}" "${CLR_INFO}[INFO]${CLR_RST} ${_msg}" )" 303 | fi 304 | else 305 | echo "Error, unknown log level: ${_lvl}" 306 | exit ${EXIT_UNKN} 307 | fi 308 | } 309 | 310 | 311 | 312 | 313 | # Aggregate nagios exit code. 314 | # OK < Warning < Error < Unknown 315 | # 316 | # @param integer The current exit code. 317 | # @param integer The new exit code 318 | # @output integer The combined exit code 319 | merge_exit_codes() { 320 | _curr_exit="$1" 321 | _next_exit="$2" 322 | 323 | # OK 324 | if [ "${_curr_exit}" = "0" ]; then 325 | _curr_exit="${_next_exit}" 326 | # Warning 327 | elif [ "${_curr_exit}" = "1" ]; then 328 | if [ "${_next_exit}" = "0" ]; then 329 | _curr_exit="1" 330 | elif [ "${_next_exit}" = "1" ]; then 331 | _curr_exit="1" 332 | elif [ "${_next_exit}" = "2" ]; then 333 | _curr_exit="2" 334 | elif [ "${_next_exit}" = "3" ]; then # UNKNOWN -> WARNING 335 | _curr_exit="1" 336 | fi 337 | # Error 338 | elif [ "${_curr_exit}" = "2" ]; then 339 | _curr_exit="2" 340 | # Unknown 341 | elif [ "${_curr_exit}" = "3" ]; then 342 | if [ "${_next_exit}" = "0" ]; then 343 | _curr_exit="3" 344 | elif [ "${_next_exit}" = "1" ]; then 345 | _curr_exit="1" 346 | elif [ "${_next_exit}" = "2" ]; then 347 | _curr_exit="2" 348 | elif [ "${_next_exit}" = "3" ]; then # UNKNOWN -> WARNING 349 | _curr_exit="3" 350 | fi 351 | fi 352 | echo "${_curr_exit}" 353 | } 354 | 355 | 356 | # Merge two texts with a delimiter 357 | # 358 | # @param string The current text 359 | # @param string The new text 360 | # @param string (Optional) The separator 361 | # @output stringr The combined text 362 | merge_text() { 363 | _curr_text="${1}" 364 | _next_text="${2}" 365 | _separator="${3:-}" 366 | 367 | if [ "${_separator}" = "" ]; then 368 | _separator="\n" 369 | fi 370 | 371 | if [ "${_curr_text}" = "" ]; then 372 | _curr_text="${_next_text}" 373 | else 374 | _curr_text="${_curr_text}${_separator}${_next_text}" 375 | fi 376 | echo "${_curr_text}" 377 | } 378 | 379 | 380 | # @param 381 | gpg_hash_in_gpg_hash_comma_sep_str() { 382 | _one_hash="${1}" 383 | _many_hash="${2}" 384 | 385 | for i in $( echo "${_many_hash}" | sed 's/,/\n/g' ); do 386 | if [ "${_one_hash}" = "${i}" ]; then 387 | # Key was found 388 | return 0 389 | fi 390 | done 391 | 392 | # No key was found 393 | return 1 394 | } 395 | 396 | ################################################################################ 397 | # Helper functions 398 | ################################################################################ 399 | 400 | # Run wrapper to make sure no aliases are used. 401 | # 402 | # @param string The command to execute 403 | # @return integer Unix exit code 404 | run() { 405 | _cmd="${1}" 406 | sh -c "LANG=C LC_ALL=C ${_cmd}" 407 | } 408 | 409 | 410 | 411 | ################################################################################ 412 | # Git helper functions 413 | ################################################################################ 414 | 415 | # Check if a given path is a valid git repository. 416 | # 417 | # @param string Git repository path 418 | # @return integer Unix exit code 419 | is_git_dir() { 420 | _path="${1}" 421 | 422 | if [ ! -d "${_path}" ]; then 423 | return 1 424 | fi 425 | 426 | if ! run "cd ${_path} && git rev-parse --is-inside-work-tree" > /dev/null 2>&1; then 427 | return 1 428 | fi 429 | 430 | return 0 431 | } 432 | 433 | # Check if a remote exists 434 | # 435 | # @param string Git repository path 436 | # @return integer Unix exit code 437 | has_remote() { 438 | _path="${1}" 439 | 440 | if ! run "cd ${_path} && git ls-remote --quiet > /dev/null 2>&1"; then 441 | return 1 442 | else 443 | return 0 444 | fi 445 | } 446 | 447 | 448 | # Check if repository contains submodules. 449 | # Outputs number of submodules 450 | # Exits failure if no submodules 451 | # 452 | # @param string Git repository path 453 | # @output integer Number of submodules 454 | # @return integer Unix exit code 455 | has_submodule() { 456 | _path="${1}" 457 | 458 | _modules="$( run "cd ${_path} && git submodule | wc -l | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g'" )" 459 | 460 | if [ "${_modules}" = "0" ]; then 461 | echo "0" 462 | return 1 463 | else 464 | echo "${_modules}" 465 | return 0 466 | fi 467 | } 468 | 469 | # Returns the current branch name of a git repository 470 | # or an empty string if not on a branch. 471 | # Success is determined by the unix exit code. 472 | # 473 | # @param string Git repository path 474 | # @output string Current branch name or empty string 475 | # @return integer Unix exit code 476 | get_current_branch() { 477 | _path="${1}" 478 | 479 | _branch="$( run "cd ${_path} && git branch --no-color | grep '^*' | sed 's/^*[[:space:]]*//g' | sed 's/^(.*)$/(nobranch)/g'" )" 480 | if [ "${_branch}" != "(nobranch)" ]; then 481 | echo "${_branch}" 482 | return 0 483 | else 484 | echo 485 | return 1 486 | fi 487 | } 488 | 489 | # Returns the current tag name of a git repository 490 | # or an empty string if not on a tag. 491 | # Success is determined by the unix exit code. 492 | # 493 | # @param string Git repository path 494 | # @output string Current tag name or empty string 495 | # @return integer Unix exit code 496 | get_current_tag() { 497 | _path="${1}" 498 | 499 | _tag="$( run "cd ${_path} && git log --pretty=format:'%d' --abbrev-commit --date=short -1 | grep -Eo 'tag:.*?,' | sed 's/,.*$//g' | sed 's/tag:[[:space:]]*//g'" )" 500 | if [ "${_tag}" = "" ]; then 501 | echo 502 | return 1 503 | else 504 | echo "${_tag}" 505 | return 0 506 | fi 507 | } 508 | 509 | # Returns the current commit hash of a git repository 510 | # or an empty string if no commit exists. 511 | # Success is determined by the unix exit code. 512 | # 513 | # @param string Git repository path 514 | # @output string Current commit hash or empty string 515 | # @return integer Unix exit code 516 | get_current_commit() { 517 | _path="${1}" 518 | 519 | # In case it fails... 520 | if ! _commit="$( run "cd ${_path} && git log --raw -1 " )"; then 521 | echo 522 | return 1 523 | else 524 | _commit="$( echo "${_commit}" | head -1 | grep commit | sed 's/^commit[[:space:]]*//g' )" 525 | 526 | # In case head/grep/sed were unable to extract it... 527 | if [ "${_commit}" = "" ]; then 528 | echo 529 | return 1 530 | else 531 | echo "${_commit}" 532 | return 0 533 | fi 534 | fi 535 | } 536 | 537 | 538 | 539 | 540 | 541 | _gpg_get_HEAD_raw_gpg_info() { 542 | _path="${1}" 543 | 544 | # If currently on a tag, verify this tag 545 | if get_current_tag "${_path}" >/dev/null 2>&1; then 546 | _tag="$( run "cd ${_path} && git tag --contains" )" 547 | _gpg="$( run "cd ${_path} && git verify-tag --raw ${_tag} 2>&1 || true" )" 548 | 549 | # If not on a tag, get current commit and verify it 550 | else 551 | _commit="$( run "cd ${_path} && git log -1 | head -1 | grep commit | sed 's/^commit[[:space:]]*//g'" )" 552 | _gpg="$( run "cd ${_path} && git verify-commit --raw ${_commit} 2>&1 || true" )" 553 | fi 554 | 555 | # No output, no gpg info 556 | if [ "${_gpg}" = "" ]; then 557 | echo 558 | return 1 559 | else 560 | echo "${_gpg}" 561 | return 0 562 | fi 563 | } 564 | 565 | gpg_get_HEAD_sign_hash() { 566 | _path="${1}" 567 | 568 | 569 | # Get raw GPG info 570 | if ! _gpg="$( _gpg_get_HEAD_raw_gpg_info "${_path}" )"; then 571 | echo 572 | return 1 573 | fi 574 | 575 | # Extract signature (valid or invalid) 576 | # This simply tells if it is signed at all or not 577 | # If _sig is empty (nothing found by grep), it was not signed. 578 | _sig="$( echo "${_gpg}" | grep -Ei 'ERRSIG|GOODSIG' | awk '{print $3}' || true )" 579 | 580 | 581 | if [ "${_sig}" = "" ]; then 582 | echo "" 583 | return 1 584 | else 585 | # Remove first 8 characters from signature. 586 | # Only get the last part 587 | echo "${_sig}" | sed 's/^........//g' 588 | return 0 589 | fi 590 | } 591 | gpg_get_HEAD_sign_mail() { 592 | _path="${1}" 593 | 594 | 595 | # Get raw GPG info 596 | if ! _gpg="$( _gpg_get_HEAD_raw_gpg_info "${_path}" )"; then 597 | echo 598 | return 1 599 | fi 600 | 601 | # Extract Email of signee 602 | # This simply tells if the public key of the signee is available. 603 | # If no email can be extracted, the public key of this commit is missing 604 | _email="$( echo "${_gpg}" | grep -Eo '<.*@.*>' | sed 's/^$//g' || true )" 605 | 606 | 607 | if [ "${_email}" = "" ]; then 608 | echo "" 609 | return 1 610 | else 611 | echo "${_email}" 612 | return 0 613 | fi 614 | } 615 | gpg_get_HEAD_sign_name() { 616 | _path="${1}" 617 | 618 | 619 | # Get raw GPG info 620 | if ! _gpg="$( _gpg_get_HEAD_raw_gpg_info "${_path}" )"; then 621 | echo 622 | return 1 623 | fi 624 | 625 | # Extract Email of signee 626 | # This simply tells if the public key of the signee is available. 627 | # If no email can be extracted, the public key of this commit is missing 628 | _name="$( echo "${_gpg}" | grep -E 'GOODSIG' | awk '{$1="";$2="";$3="";$NF=""; print}' | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g' || true )" 629 | 630 | 631 | if [ "${_name}" = "" ]; then 632 | echo "" 633 | return 1 634 | else 635 | echo "${_name}" 636 | return 0 637 | fi 638 | } 639 | gpg_get_HEAD_trust_level() { 640 | _path="${1}" 641 | 642 | 643 | # Get raw GPG info 644 | if ! _gpg="$( _gpg_get_HEAD_raw_gpg_info "${_path}" )"; then 645 | echo 646 | return 1 647 | fi 648 | 649 | # Extract trust level 650 | _trust="$( echo "${_gpg}" | grep -Eo 'TRUST_.*' | sed 's/TRUST_//g' || true )" 651 | 652 | 653 | if [ "${_trust}" = "" ]; then 654 | echo "" 655 | return 1 656 | else 657 | echo "${_trust}" 658 | return 0 659 | fi 660 | } 661 | 662 | 663 | 664 | 665 | 666 | ################################################################################ 667 | # Git functions 668 | ################################################################################ 669 | 670 | # Check if your repository is clean. 671 | # You also have to specify whether or not to ignore submodules. 672 | # 673 | # @param string Git repository path 674 | # @param boolean Ignore submodules? (1: Ignore, 0: Don't Ignore) 675 | # @return integer Unix exit code 676 | git_status() { 677 | _path="${1}" 678 | _ign_sub="${2}" 679 | 680 | if [ "${_ign_sub}" = "1" ]; then 681 | _ign_sub="--ignore-submodules" 682 | else 683 | _ign_sub="" 684 | fi 685 | 686 | _output="$( run "cd ${_path} && git status --porcelain ${_ign_sub}" )" 687 | if [ "${_output}" != "" ]; then 688 | return 1 689 | else 690 | return 0 691 | fi 692 | } 693 | 694 | 695 | 696 | # If the git repo is on a branch, check if it 697 | # is in sync with the remote branch (no differences). 698 | # You also have to specify whether or not to ignore submodules. 699 | # 700 | # If the git repo is not on a branch return success. 701 | # @param string Git repository path 702 | # @param string Remote name (usually 'origin') 703 | # @param boolean Ignore submodules? (1: Ignore, 0: Don't Ignore) 704 | # @return integer Unix exit code 705 | git_branch_is_in_sync_with_remote() { 706 | _path="${1}" 707 | _remote="${2}" 708 | _ign_sub="${3}" 709 | 710 | if [ "${_ign_sub}" = "1" ]; then 711 | _ign_sub="--ignore-submodules" 712 | else 713 | _ign_sub="" 714 | fi 715 | 716 | if _branch="$( get_current_branch "${_path}" )"; then 717 | _branch="$( run "cd ${_path} && git branch --no-color | grep '^*' | sed 's/^*[[:space:]]*//g'" )" 718 | 719 | # Try to fetch remote 15 times in case something goes wrong 720 | for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do 721 | # Break on early success 722 | set +e 723 | if run "cd ${_path} && git fetch --no-recurse-submodules --no-tags --quiet > /dev/null 2>&1"; then 724 | set -e # Re-enable break on error 725 | break 726 | fi 727 | done 728 | 729 | # Try to diff remote 9 times in case something goes wrong 730 | for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do 731 | set +e 732 | _diff="$( run "cd ${_path} && git diff ${_ign_sub} ${_remote}/${_branch} 2>/dev/null"; )" 733 | _exit="$?" 734 | if [ "${_exit}" = "0" ]; then 735 | set -e # Re-enable break on error 736 | break 737 | fi 738 | done 739 | 740 | if [ "${_diff}" != "" ]; then 741 | return 1 742 | else 743 | return 0 744 | fi 745 | fi 746 | 747 | # Not on a branch? Return success. 748 | return 0 749 | } 750 | 751 | 752 | 753 | 754 | #################################################################################################### 755 | #################################################################################################### 756 | ### 757 | ### M A I N E N T R Y P O I N T : A R G U M E N T S 758 | ### 759 | #################################################################################################### 760 | #################################################################################################### 761 | 762 | 763 | ############################################################ 764 | # Retrieve arguments 765 | ############################################################ 766 | 767 | while test -n "${1:-}"; do 768 | 769 | case "$1" in 770 | 771 | # ---- 1. Git repository path 772 | -d) 773 | # Get next arg in list (Path) 774 | shift 775 | GIT_REPOSITORY_PATH="${1}" 776 | if ! is_git_dir "${GIT_REPOSITORY_PATH}"; then 777 | echo "Not a valid git directory" 778 | exit $EXIT_UNKN 779 | fi 780 | ;; 781 | 782 | # ---- 2. Output options 783 | -v) 784 | OUTPUT_VERBOSE=1 785 | ;; 786 | -n) 787 | OUTPUT_NAGIOS=1 788 | # Get project name 789 | if [ "${2:-}" = "" ]; then 790 | echo "Error, -lnrequires a name to be specified" 791 | exit $EXIT_UNKN 792 | fi 793 | shift 794 | OUTPUT_NAGIOS_NAME="${1}" 795 | ;; 796 | -l) 797 | OUTPUT_LOGFILE=1 798 | 799 | # Get path name 800 | if [ "${2:-}" = "" ]; then 801 | echo "Error, -l requires a logfile path to be specified" 802 | exit $EXIT_UNKN 803 | fi 804 | shift 805 | OUTPUT_LOGFILE_NAME="${1}" 806 | ;; 807 | 808 | # ---- 3. Check status 809 | -s) 810 | if [ "${CHECK_STATUS_WITHOUT_SUB}" = "1" ]; then 811 | echo "Error, -S and -s are mutually exclusive." 812 | echo "Only specify one of then." 813 | exit $EXIT_UNKN 814 | fi 815 | CHECK_STATUS_WITH_SUB=1 816 | ;; 817 | -S) 818 | if [ "${CHECK_STATUS_WITH_SUB}" = "1" ]; then 819 | echo "Error, -S and -s are mutually exclusive." 820 | echo "Only specify one of then." 821 | exit $EXIT_UNKN 822 | fi 823 | CHECK_STATUS_WITHOUT_SUB=1 824 | ;; 825 | 826 | # ---- 4. Check remote 827 | -r) 828 | if [ "${CHECK_REMOTE_WITHOUT_SUB}" = "1" ]; then 829 | echo "Error, -R and -r are mutually exclusive." 830 | echo "Only specify one of then." 831 | exit $EXIT_UNKN 832 | fi 833 | CHECK_REMOTE_WITH_SUB=1 834 | 835 | # Get remote name 836 | if [ "${2:-}" = "" ]; then 837 | echo "Error, -r requires remote name to be specified" 838 | exit $EXIT_UNKN 839 | fi 840 | shift 841 | CHECK_REMOTE_NAME="${1}" 842 | ;; 843 | -R) 844 | if [ "${CHECK_REMOTE_WITH_SUB}" = "1" ]; then 845 | echo "Error, -R and -r are mutually exclusive." 846 | echo "Only specify one of then." 847 | exit $EXIT_UNKN 848 | fi 849 | CHECK_REMOTE_WITHOUT_SUB=1 850 | 851 | # Get remote name 852 | if [ "${2:-}" = "" ]; then 853 | echo "Error, -R requires remote name to be specified" 854 | exit $EXIT_UNKN 855 | fi 856 | shift 857 | CHECK_REMOTE_NAME="${1}" 858 | ;; 859 | 860 | # ---- 5. Check branch 861 | -b) 862 | if [ "${CHECK_TAG}" = "1" ] || [ "${CHECK_TAG_ANY}" = "1" ] || [ "${CHECK_BRANCH}" = "1" ]; then 863 | echo "Error, -b, -B, -t and -T are mutually exclusive." 864 | echo "Only specify one of then." 865 | exit $EXIT_UNKN 866 | fi 867 | CHECK_BRANCH_ANY=1 868 | ;; 869 | -B) 870 | if [ "${CHECK_TAG}" = "1" ] || [ "${CHECK_TAG_ANY}" = "1" ] || [ "${CHECK_BRANCH_ANY}" = "1" ]; then 871 | echo "Error, -b, -B, -t and -T are mutually exclusive." 872 | echo "Only specify one of then." 873 | exit $EXIT_UNKN 874 | fi 875 | CHECK_BRANCH=1 876 | 877 | if [ "${2:-}" = "" ]; then 878 | echo "Error, -B requires branch name to be specified" 879 | exit $EXIT_UNKN 880 | fi 881 | shift 882 | CHECK_BRANCH_NAME="${1}" 883 | ;; 884 | 885 | # ---- 6. Check tag 886 | -t) 887 | if [ "${CHECK_BRANCH}" = "1" ] || [ "${CHECK_BRANCH_ANY}" = "1" ] || [ "${CHECK_TAG}" = "1" ]; then 888 | echo "Error, -b, -B, -t and -T are mutually exclusive." 889 | echo "Only specify one of then." 890 | exit $EXIT_UNKN 891 | fi 892 | CHECK_TAG_ANY=1 893 | ;; 894 | -T) 895 | if [ "${CHECK_BRANCH}" = "1" ] || [ "${CHECK_BRANCH_ANY}" = "1" ] || [ "${CHECK_TAG_ANY}" = "1" ]; then 896 | echo "Error, -b, -B, -t and -T are mutually exclusive." 897 | echo "Only specify one of then." 898 | exit $EXIT_UNKN 899 | fi 900 | CHECK_TAG=1 901 | 902 | if [ "${2:-}" = "" ]; then 903 | echo "Error, -T requires tag name to be specified" 904 | exit $EXIT_UNKN 905 | fi 906 | shift 907 | CHECK_TAG_NAME="${1}" 908 | ;; 909 | 910 | # ---- 7. Check gpg 911 | -g) 912 | if [ "${CHECK_GPG}" = "1" ]; then 913 | echo "Error, -g and -G are mutually exclusive." 914 | echo "Only specify one of then." 915 | exit $EXIT_UNKN 916 | fi 917 | CHECK_GPG_ANY=1 918 | ;; 919 | -G) 920 | if [ "${CHECK_GPG_ANY}" = "1" ]; then 921 | echo "Error, -g and -G are mutually exclusive." 922 | echo "Only specify one of then." 923 | exit $EXIT_UNKN 924 | fi 925 | CHECK_GPG=1 926 | 927 | if [ "${2:-}" = "" ]; then 928 | echo "Error, -G requires key-id to be specified" 929 | exit $EXIT_UNKN 930 | fi 931 | shift 932 | CHECK_GPG_KEYS="${1}" 933 | ;; 934 | 935 | # ---- 8. Version/Help 936 | -V) 937 | print_version 938 | exit $EXIT_SUCC 939 | ;; 940 | -h) 941 | print_usage 942 | exit $EXIT_SUCC 943 | ;; 944 | 945 | # ---- 9. Unknown 946 | *) 947 | printf "Unknown argument: %s\n" "$1" 948 | print_usage 949 | exit $EXIT_UNKN 950 | ;; 951 | esac 952 | shift 953 | done 954 | 955 | 956 | ############################################################ 957 | # Validate arguments 958 | ############################################################ 959 | 960 | 961 | if [ "${GIT_REPOSITORY_PATH}" = "" ]; then 962 | echo "Error, -d is mandatory" 963 | exit $EXIT_UNKN 964 | fi 965 | 966 | if ! is_git_dir "${GIT_REPOSITORY_PATH}"; then 967 | echo "Error, Not a git directory: ${GIT_REPOSITORY_PATH}" 968 | exit $EXIT_UNKN 969 | fi 970 | 971 | # Logfile output requires nagios log format 972 | if [ "${OUTPUT_LOGFILE}" = "1" ]; then 973 | if [ "${OUTPUT_NAGIOS}" != "1" ]; then 974 | echo "Error, Setting '-l' quires '-n'!" 975 | exit $EXIT_UNKN 976 | fi 977 | fi 978 | 979 | # Disable colors for nagios output 980 | if [ "${OUTPUT_NAGIOS}" = "1" ]; then 981 | CLR_SUCC= 982 | CLR_WARN= 983 | CLR_CRIT= 984 | CLR_UNKN= 985 | CLR_INFO= 986 | CLR_RST= 987 | fi 988 | 989 | 990 | 991 | #################################################################################################### 992 | #################################################################################################### 993 | ### 994 | ### M A I N E N T R Y P O I N T : C H E C K S 995 | ### 996 | #################################################################################################### 997 | #################################################################################################### 998 | 999 | 1000 | 1001 | ############################################################ 1002 | # Get some information 1003 | ############################################################ 1004 | 1005 | # HEAD 1006 | if GIT_BRANCH="$( get_current_branch "${GIT_REPOSITORY_PATH}" )"; then 1007 | GIT_HEAD="on branch '${GIT_BRANCH}'" 1008 | elif GIT_TAG="$( get_current_tag "${GIT_REPOSITORY_PATH}" )"; then 1009 | GIT_HEAD="on tag '${GIT_TAG}'" 1010 | else 1011 | GIT_HEAD="detached at: $( get_current_commit "${GIT_REPOSITORY_PATH}" )" 1012 | fi 1013 | # GPG Signed 1014 | if GIT_SIGNED="$( gpg_get_HEAD_sign_hash "${GIT_REPOSITORY_PATH}" )"; then 1015 | GIT_SIGNED="signed ${GIT_SIGNED}" 1016 | else 1017 | GIT_SIGNED="unsigned" 1018 | fi 1019 | # Number of submodules 1020 | GIT_SUBMODULES="$( has_submodule "${GIT_REPOSITORY_PATH}" || true )" 1021 | 1022 | log "info" "Bin: $(git --version | head -1)" 1023 | log "info" "Path: ${GIT_REPOSITORY_PATH}" 1024 | log "info" "Submodules: ${GIT_SUBMODULES}" 1025 | log "info" "HEAD: ${GIT_HEAD}" 1026 | log "info" "GPG: ${GIT_SIGNED}" 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | ############################################################ 1033 | # Do the checks 1034 | ############################################################ 1035 | 1036 | ## 1037 | ## 01. Check git status 1038 | ## 1039 | if [ "${CHECK_STATUS_WITH_SUB}" = "1" ]; then 1040 | if git_status "${GIT_REPOSITORY_PATH}" "1"; then # (without submodule) 1041 | log "succ" "Git status: clean" 1042 | else 1043 | log "crit" "Git status: unclean" 1044 | fi 1045 | if [ "${GIT_SUBMODULES}" != "0" ]; then 1046 | if git_status "${GIT_REPOSITORY_PATH}" "0"; then # (including submodule) 1047 | log "succ" "Git status: submodule(s) clean" 1048 | else 1049 | log "crit" "Git status: submodule(s) unclean" 1050 | fi 1051 | fi 1052 | 1053 | elif [ "${CHECK_STATUS_WITHOUT_SUB}" = "1" ]; then 1054 | if git_status "${GIT_REPOSITORY_PATH}" "1"; then # (without submodule) 1055 | log "succ" "Git status: clean" 1056 | else 1057 | log "crit" "Git status: unclean" 1058 | fi 1059 | fi 1060 | 1061 | 1062 | ## 1063 | ## 02. Check git remote diff 1064 | ## 1065 | if [ "${CHECK_REMOTE_WITH_SUB}" = "1" ]; then 1066 | 1067 | # 0.2.1 Check if there is a remote in the first place 1068 | if ! has_remote "${GIT_REPOSITORY_PATH}"; then 1069 | log "crit" "Git remote: No remote available" 1070 | else 1071 | if git_branch_is_in_sync_with_remote "${GIT_REPOSITORY_PATH}" "${CHECK_REMOTE_NAME}" "1"; then # (without submodule) 1072 | log "succ" "Git remote: equals with '${CHECK_REMOTE_NAME}'" 1073 | else 1074 | log "crit" "Git remote: differs from '${CHECK_REMOTE_NAME}'" 1075 | fi 1076 | if [ "${GIT_SUBMODULES}" != "0" ]; then 1077 | if git_branch_is_in_sync_with_remote "${GIT_REPOSITORY_PATH}" "${CHECK_REMOTE_NAME}" "0"; then # (including submodule) 1078 | log "succ" "Git remote: submodule(s) equals with '${CHECK_REMOTE_NAME}'" 1079 | else 1080 | log "crit" "Git remote: submodule(s) differs from '${CHECK_REMOTE_NAME}'" 1081 | fi 1082 | fi 1083 | fi 1084 | 1085 | elif [ "${CHECK_REMOTE_WITHOUT_SUB}" = "1" ]; then 1086 | 1087 | # 0.2.1 Check if there is a remote in the first place 1088 | if ! has_remote "${GIT_REPOSITORY_PATH}"; then 1089 | log "crit" "Git remote: No remote available" 1090 | else 1091 | if git_branch_is_in_sync_with_remote "${GIT_REPOSITORY_PATH}" "${CHECK_REMOTE_NAME}" "1"; then # (without submodule) 1092 | log "succ" "Git remote: equals with '${CHECK_REMOTE_NAME}'" 1093 | else 1094 | log "crit" "Git remote: differs from '${CHECK_REMOTE_NAME}'" 1095 | fi 1096 | fi 1097 | fi 1098 | 1099 | 1100 | ## 1101 | ## 03. Check if git is on a branch (any or specific) 1102 | ## 1103 | if [ "${CHECK_BRANCH_ANY}" = "1" ]; then 1104 | if GIT_BRANCH="$( get_current_branch "${GIT_REPOSITORY_PATH}" )"; then 1105 | log "succ" "Git Branch: on branch '${GIT_BRANCH}'" 1106 | else 1107 | log "crit" "Git Branch: not on any branch" 1108 | fi 1109 | 1110 | elif [ "${CHECK_BRANCH}" = "1" ]; then 1111 | if GIT_BRANCH="$( get_current_branch "${GIT_REPOSITORY_PATH}" )"; then 1112 | if [ "${GIT_BRANCH}" = "${CHECK_BRANCH_NAME}" ]; then 1113 | log "succ" "Git Branch: on branch '${GIT_BRANCH}'" 1114 | else 1115 | log "crit" "Git Branch: on branch '${GIT_BRANCH}', but should be on: '${CHECK_BRANCH_NAME}'" 1116 | fi 1117 | else 1118 | log "crit" "Git Branch: not on any branch" 1119 | fi 1120 | fi 1121 | 1122 | 1123 | 1124 | ## 1125 | ## 04. Check if git is on a tag (any or specific) 1126 | ## 1127 | if [ "${CHECK_TAG_ANY}" = "1" ]; then 1128 | if GIT_TAG="$( get_current_tag "${GIT_REPOSITORY_PATH}" )"; then 1129 | log "succ" "Git Tag: on tag '${GIT_TAG}'" 1130 | else 1131 | log "crit" "Git Tag: not on any tag" 1132 | fi 1133 | 1134 | elif [ "${CHECK_TAG}" = "1" ]; then 1135 | if GIT_TAG="$( get_current_tag "${GIT_REPOSITORY_PATH}" )"; then 1136 | if [ "${GIT_TAG}" = "${CHECK_TAG_NAME}" ]; then 1137 | log "succ" "Git Tag: on tag '${GIT_TAG}'" 1138 | else 1139 | log "crit" "Git Tag: on tag '${GIT_TAG}', but should be on: '${CHECK_TAG_NAME}'" 1140 | fi 1141 | else 1142 | log "crit" "Git Tag: not on any tag" 1143 | fi 1144 | fi 1145 | 1146 | 1147 | 1148 | ## 1149 | ## 05. Check valid GPG signature of current commit 1150 | ## 1151 | if [ "${CHECK_GPG_ANY}" = "1" ]; then 1152 | 1153 | # Signed 1154 | if sign_hash="$( gpg_get_HEAD_sign_hash "${GIT_REPOSITORY_PATH}" )"; then 1155 | 1156 | log "succ" "GPG Signed: Yes with key: ${sign_hash}" 1157 | 1158 | # Has pubkey 1159 | if sign_name="$( gpg_get_HEAD_sign_name "${GIT_REPOSITORY_PATH}" )" && sign_mail="$( gpg_get_HEAD_sign_mail "${GIT_REPOSITORY_PATH}" )"; then 1160 | log "succ" "GPG Pubkey: available" 1161 | log "succ" "GPG Signer: ${sign_name} <${sign_mail}>" 1162 | 1163 | # Trust Level 1164 | if trust_level="$( gpg_get_HEAD_trust_level "${GIT_REPOSITORY_PATH}" )"; then 1165 | if [ "${trust_level}" = "UNDEFINED" ]; then 1166 | log "warn" "GPG Trust: Not trusted" 1167 | else 1168 | log "succ" "GPG Trust: ${trust_level}" 1169 | fi 1170 | else 1171 | log "warn" "GPG Trust: unable to get trust level" 1172 | fi 1173 | else 1174 | log "warn" "GPG Pubkey: not available" 1175 | fi 1176 | 1177 | # Unsigned 1178 | else 1179 | log "crit" "GPG Signed: unsigned" 1180 | 1181 | fi 1182 | elif [ "${CHECK_GPG}" = "1" ]; then 1183 | 1184 | # Signed 1185 | if sign_hash="$( gpg_get_HEAD_sign_hash "${GIT_REPOSITORY_PATH}" )"; then 1186 | 1187 | if gpg_hash_in_gpg_hash_comma_sep_str "${sign_hash}" "${CHECK_GPG_KEYS}"; then 1188 | log "succ" "GPG Signed: Yes with expected key: ${sign_hash}" 1189 | 1190 | # Has pubkey 1191 | if sign_name="$( gpg_get_HEAD_sign_name "${GIT_REPOSITORY_PATH}" )" && sign_mail="$( gpg_get_HEAD_sign_mail "${GIT_REPOSITORY_PATH}" )"; then 1192 | log "succ" "GPG Pubkey: available" 1193 | log "succ" "GPG Signer: ${sign_name} <${sign_mail}>" 1194 | 1195 | # Trust Level 1196 | if trust_level="$( gpg_get_HEAD_trust_level "${GIT_REPOSITORY_PATH}" )"; then 1197 | if [ "${trust_level}" = "UNDEFINED" ]; then 1198 | log "warn" "GPG Trust: Not trusted" 1199 | else 1200 | log "succ" "GPG Trust: ${trust_level}" 1201 | fi 1202 | else 1203 | log "warn" "GPG Trust: unable to get trust level" 1204 | fi 1205 | else 1206 | log "warn" "GPG Pubkey: not available" 1207 | fi 1208 | else 1209 | log "crit" "GPG Signed: Signed with ${sign_hash}, but not in list of valid keys" 1210 | fi 1211 | 1212 | # Unsigned 1213 | else 1214 | log "crit" "GPG Signed: unsigned" 1215 | fi 1216 | fi 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | ## 1224 | ## 06. Create Nagios output? 1225 | ## 1226 | if [ "${OUTPUT_NAGIOS}" = "1" ]; then 1227 | 1228 | NAGIOS_STATUS_LINE= 1229 | if [ "${PROGRAM_EXIT_CODE}" = "${EXIT_SUCC}" ]; then 1230 | NAGIOS_STATUS_LINE="[SUCCESS] ${OUTPUT_NAGIOS_NAME} git repo is healthy." 1231 | elif [ "${PROGRAM_EXIT_CODE}" = "${EXIT_WARN}" ]; then 1232 | NAGIOS_STATUS_LINE="[WARNING] ${OUTPUT_NAGIOS_NAME} git repo has warnings." 1233 | elif [ "${PROGRAM_EXIT_CODE}" = "${EXIT_CRIT}" ]; then 1234 | NAGIOS_STATUS_LINE="[CRITICAL] ${OUTPUT_NAGIOS_NAME} git repo has errors." 1235 | else 1236 | NAGIOS_STATUS_LINE="[UNKNOWN] ${OUTPUT_NAGIOS_NAME} git repo is unknown." 1237 | fi 1238 | 1239 | # Prepend Nagios status line 1240 | PROGRAM_EXIT_TEXT="$( merge_text "${NAGIOS_STATUS_LINE}" "${PROGRAM_EXIT_TEXT}" )" 1241 | fi 1242 | 1243 | 1244 | ## 1245 | ## 07. Log to file or output? 1246 | ## 1247 | if [ "${OUTPUT_LOGFILE}" = "1" ]; then 1248 | echo "${PROGRAM_EXIT_TEXT}" > "${OUTPUT_LOGFILE_NAME}" 1249 | echo "${PROGRAM_EXIT_CODE}" >> "${OUTPUT_LOGFILE_NAME}" 1250 | exit "${EXIT_SUCC}" 1251 | else 1252 | printf "%s${PROGRAM_EXIT_TEXT}\n" "" 1253 | exit "${PROGRAM_EXIT_CODE}" 1254 | fi 1255 | --------------------------------------------------------------------------------