Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / git / git_commit_behind @ 646a6c69

Historique | Voir | Annoter | Télécharger (9,4 ko)

1 a26b9e8d Neraud
#! /usr/bin/env python3
2
3
"""=cut
4
=head1 NAME
5
6
git_commit_behind - Munin plugin to monitor local git repositories and report
7
how many commits behind their remote they are
8
9
=head1 NOTES
10
11
This plugin is similar to how apt_all works for apt packages.
12
13
To be able to check how behind a git repository is, we need to run git fetch.
14 1027f5e5 Neraud
To avoid fetching all repos every 5 minutes (for each munin period) and thus
15
slowing down the data collection, the git fetch operation is only randomly
16
triggered (based on env.update.probability).
17
In case of very time-consuming update operations, you can run them in a
18
separate cron job.
19 a26b9e8d Neraud
20
=head1 REQUIREMENTS
21
22
 - Python3
23
 - Git
24
25
=head1 INSTALLATION
26
27
Link this plugin, as usual.
28
For example :
29
  ln -s /path/to/git_commit_behind /etc/munin/plugins/git_commit_behind
30
31 d079f0fa Neraud
If you wish to update the repositories via cron and not during the plugin
32
execution (cf CONFIGURATION section), you need a dedicated cron job.
33 a26b9e8d Neraud
34
For example, you can use the following cron :
35
36 d079f0fa Neraud
# If the git_commit_behind plugin is enabled, fetch git repositories randomly
37
# according to the plugin configuration.
38
# By default : once an hour (12 invocations an hour, 1 in 12 chance that the
39
# update will happen), but ensure that there will never be more than two hours
40 a26b9e8d Neraud
# (7200 seconds) interval between updates.
41 d079f0fa Neraud
*/5 * * * * root if [ -x /etc/munin/plugins/git_commit_behind ]; then /usr/sbin/munin-run git_commit_behind update >/dev/null; fi
42 a26b9e8d Neraud
43
=head1 CONFIGURATION
44
45
Use your "/etc/munin/plugin-conf.d/munin-node" to configure this plugin.
46
    [git_commit_behind]
47 0d538451 Neraud
    user [user]
48 a26b9e8d Neraud
    env.git_path /path/to/git
49 d079f0fa Neraud
    env.update.mode [munin|cron]
50
    env.update.probability 12
51
    env.update.maxinterval 7200
52 a26b9e8d Neraud
53 0d538451 Neraud
user [user] : required, the owner of the repository checkouts
54
    in case of multiple different owners, use root
55 7bc4d133 Neraud
env.git_path : optional (default : /usr/bin/git), the path to the git binary.
56 d079f0fa Neraud
env.update.mode : optional (default : munin), the update mode.
57
    munin : repositories are git fetched during the pugin execution
58
    cron : a dedicated cron job needs to be used to update the repositories
59
env.update.probability : optional (default : 12),
60
    runs the update randomly (1 in <probability> chances)
61
env.update.maxinterval : optional (default : 7200),
62
    ensures that the update is run at least every <maxinterval> seconds
63
64 7bc4d133 Neraud
65 a26b9e8d Neraud
Then, for each repository you want to check, you need the following
66
configuration block under the git_commit_behind section
67
    env.repo.[repoCode].path /path/to/local/repo
68
    env.repo.[repoCode].name Repo Name
69
    env.repo.[repoCode].user user
70
    env.repo.[repoCode].warning 10
71
    env.repo.[repoCode].critical 100
72
73
[repoCode] can only contain letters, numbers and underscores.
74
75
path : mandatory, the local path to your git repository
76
name : optional (default : [repoCode]), a cleaner name that will be displayed
77
user : optional (default : empty), the owner of the repository
78
       if set and different from the user running the plugin, the git commands
79
       will be executed as this user
80
warning : optional (default 10), the warning threshold
81
critical : optional (default 100), the critical threshold
82
83
For example :
84
85
    [git_commit_behind]
86
    user root
87
88
    env.repo.munin_contrib.path /opt/munin-contrib
89
    env.repo.munin_contrib.name Munin Contrib
90
91
    env.repo.other_repo.path /path/to/other-repo
92
    env.repo.other_repo.name Other Repo
93
94
=head1 MAGIC MARKERS
95
96
  #%# family=auto
97
  #%# capabilities=autoconf
98
99
=head1 VERSION
100
101
1.0.0
102
103
=head1 AUTHOR
104
105
Neraud (https://github.com/Neraud)
106
107
=head1 LICENSE
108
109
GPLv2
110
111
=cut"""
112
113
114
import logging
115
import os
116 e0b243ba Neraud
from pathlib import Path
117 646a6c69 Neraud
import pwd
118 a26b9e8d Neraud
from random import randint
119
import re
120 c169373f Neraud
from shlex import quote
121 a26b9e8d Neraud
from subprocess import check_output, call, DEVNULL, CalledProcessError
122
import sys
123
import time
124
125
126
plugin_version = "1.0.0"
127
128 e29c89c0 Neraud
if int(os.getenv('MUNIN_DEBUG', 0)) > 0:
129 a26b9e8d Neraud
    logging.basicConfig(level=logging.DEBUG,
130
                        format='%(asctime)s %(levelname)-7s %(message)s')
131
132 646a6c69 Neraud
current_user = pwd.getpwuid(os.geteuid())[0]
133
134 a26b9e8d Neraud
conf = {
135 d079f0fa Neraud
    'git_path':            os.getenv('git_path', '/usr/bin/git'),
136
    'state_file':          os.getenv('MUNIN_STATEFILE'),
137
    'update_mode':         os.getenv('update.mode', 'munin'),
138
    'update_probability':  int(os.getenv('update.probability', '12')),
139
    'update_maxinterval':  int(os.getenv('update.maxinterval', '7200'))
140 a26b9e8d Neraud
}
141
142 397052a8 Neraud
repo_codes = set(re.search('repo\.([^.]+)\..*', elem).group(1)
143
                 for elem in os.environ.keys() if elem.startswith('repo.'))
144 a26b9e8d Neraud
145
repos_conf = {}
146
for code in repo_codes:
147
    repos_conf[code] = {
148
        'name':        os.getenv('repo.%s.name' % code, code),
149
        'path':        os.getenv('repo.%s.path' % code, None),
150
        'user':        os.getenv('repo.%s.user' % code, None),
151
        'warning':     os.getenv('repo.%s.warning' % code, '10'),
152
        'critical':    os.getenv('repo.%s.critical' % code, '100')
153
    }
154
155
156
def print_config():
157
    print('graph_title Git repositories - Commits behind')
158
159
    print('graph_args --base 1000 -r --lower-limit 0')
160
    print('graph_vlabel number of commits behind')
161
    print('graph_scale yes')
162
    print('graph_info This graph shows the number of commits behind' +
163
          ' for each configured git repository')
164 eb9681a7 Neraud
    print('graph_category file_transfer')
165 a26b9e8d Neraud
166
    print('graph_order %s' % ' '.join(repo_codes))
167
168
    for repo_code in repos_conf.keys():
169
        print('%s.label %s' % (repo_code, repos_conf[repo_code]['name']))
170
        print('%s.warning %s' % (repo_code, repos_conf[repo_code]['warning']))
171
        print('%s.critical %s' %
172
              (repo_code, repos_conf[repo_code]['critical']))
173
174
175
def generate_git_command(repo_conf, git_command):
176 646a6c69 Neraud
    if not repo_conf['user'] or repo_conf['user'] == current_user:
177 c169373f Neraud
        cmd = [quote(conf['git_path'])] + git_command
178 a26b9e8d Neraud
    else:
179
        shell_cmd = 'cd %s ; %s %s' % (
180 c169373f Neraud
            quote(repo_conf['path']),
181
            quote(conf['git_path']),
182
            ' '.join(git_command))
183 d5caa85c Neraud
        cmd = ['su', '-', repo_conf['user'], '-s', '/bin/sh', '-c', shell_cmd]
184 a26b9e8d Neraud
    return cmd
185
186
187
def execute_git_command(repo_conf, git_command):
188
    cmd = generate_git_command(repo_conf, git_command)
189
    return check_output(cmd, cwd=repo_conf['path']).decode('utf-8').rstrip()
190
191
192 81b129dd Neraud
def print_info():
193 a26b9e8d Neraud
    if not os.access(conf['git_path'], os.X_OK):
194
        print('Git (%s) is missing, or not executable !' %
195
              conf['git_path'], file=sys.stderr)
196
        sys.exit(1)
197
198
    for repo_code in repos_conf.keys():
199
        logging.debug(' - %s' % repo_code)
200
        try:
201
            remote_branch = execute_git_command(
202
                repos_conf[repo_code],
203
                ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'])
204
            logging.debug('remote_branch = %s' % remote_branch)
205
206
            commits_behind = execute_git_command(
207
                repos_conf[repo_code],
208
                ['rev-list', 'HEAD..%s' % remote_branch, '--count'])
209
210
            print('%s.value %d' % (repo_code, int(commits_behind)))
211
        except CalledProcessError as e:
212 397052a8 Neraud
            logging.error('Error executing git command : %s', e)
213 a26b9e8d Neraud
        except FileNotFoundError as e:
214
            logging.error('Repo not found at path %s' %
215
                          repos_conf[repo_code]['path'])
216
217
218 d079f0fa Neraud
def check_update_repos(mode):
219 61241b80 Neraud
    if not conf['state_file']:
220
        logging.error('Munin state file unavailable')
221
        sys.exit(1)
222
223 d079f0fa Neraud
    if mode != conf['update_mode']:
224
        logging.debug('Wrong mode, skipping')
225
        return
226 a26b9e8d Neraud
227
    if not os.path.isfile(conf['state_file']):
228
        logging.debug('No state file -> updating')
229
        do_update_repos()
230 d079f0fa Neraud
    elif (os.path.getmtime(conf['state_file']) + conf['update_maxinterval']
231
            < time.time()):
232 a26b9e8d Neraud
        logging.debug('State file last modified too long ago -> updating')
233
        do_update_repos()
234 d079f0fa Neraud
    elif randint(1, conf['update_probability']) == 1:
235 a26b9e8d Neraud
        logging.debug('Recent state, but random matched -> updating')
236
        do_update_repos()
237
    else:
238
        logging.debug('Recent state and random missed -> skipping')
239
240
241
def do_update_repos():
242
    for repo_code in repos_conf.keys():
243
        try:
244
            logging.info('Fetching repo %s' % repo_code)
245
            execute_git_command(repos_conf[repo_code], ['fetch'])
246
        except CalledProcessError as e:
247 397052a8 Neraud
            logging.error('Error executing git command : %s', e)
248 a26b9e8d Neraud
        except FileNotFoundError as e:
249
            logging.error('Repo not found at path %s' %
250
                          repos_conf[repo_code]['path'])
251
    logging.debug('Updating the state file')
252 e0b243ba Neraud
253
    # 'touch' the state file to update its last modified date
254
    Path(conf['state_file']).touch()
255 a26b9e8d Neraud
256
257
if len(sys.argv) > 1:
258
    action = sys.argv[1]
259
    if action == 'config':
260
        print_config()
261
    elif action == 'autoconf':
262 61241b80 Neraud
        errors = []
263
264
        if not conf['state_file']:
265
            errors.append('munin state file unavailable')
266
267 a26b9e8d Neraud
        if os.access(conf['git_path'], os.X_OK):
268
            test_git = call([conf['git_path'], '--version'], stdout=DEVNULL)
269 61241b80 Neraud
            if test_git != 0:
270
                errors.append('git seems to be broken ?!')
271
        else:
272
            errors.append('git is missing or not executable')
273
274
        if errors:
275
            print('no (%s)' % ', '.join(errors))
276 a26b9e8d Neraud
        else:
277 61241b80 Neraud
            print('yes')
278 a26b9e8d Neraud
    elif action == 'version':
279
        print('Git commit behind Munin plugin, version {0}'.format(
280
            plugin_version))
281
    elif action == 'update':
282 d079f0fa Neraud
        check_update_repos('cron')
283
    else:
284 397052a8 Neraud
        logging.warn("Unknown argument '%s'" % action)
285 a26b9e8d Neraud
        sys.exit(1)
286
else:
287 d079f0fa Neraud
    if conf['update_mode'] == 'munin':
288
        check_update_repos('munin')
289 81b129dd Neraud
    print_info()