Projet

Général

Profil

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

root / plugins / git / git_commit_behind @ e29c89c0

Historique | Voir | Annoter | Télécharger (9,34 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 a26b9e8d Neraud
from random import randint
118
import re
119 c169373f Neraud
from shlex import quote
120 a26b9e8d Neraud
from subprocess import check_output, call, DEVNULL, CalledProcessError
121
import sys
122
import time
123
124
125
plugin_version = "1.0.0"
126
127 e29c89c0 Neraud
if int(os.getenv('MUNIN_DEBUG', 0)) > 0:
128 a26b9e8d Neraud
    logging.basicConfig(level=logging.DEBUG,
129
                        format='%(asctime)s %(levelname)-7s %(message)s')
130
131
conf = {
132 d079f0fa Neraud
    'git_path':            os.getenv('git_path', '/usr/bin/git'),
133
    'state_file':          os.getenv('MUNIN_STATEFILE'),
134
    'update_mode':         os.getenv('update.mode', 'munin'),
135
    'update_probability':  int(os.getenv('update.probability', '12')),
136
    'update_maxinterval':  int(os.getenv('update.maxinterval', '7200'))
137 a26b9e8d Neraud
}
138
139 397052a8 Neraud
repo_codes = set(re.search('repo\.([^.]+)\..*', elem).group(1)
140
                 for elem in os.environ.keys() if elem.startswith('repo.'))
141 a26b9e8d Neraud
142
repos_conf = {}
143
for code in repo_codes:
144
    repos_conf[code] = {
145
        'name':        os.getenv('repo.%s.name' % code, code),
146
        'path':        os.getenv('repo.%s.path' % code, None),
147
        'user':        os.getenv('repo.%s.user' % code, None),
148
        'warning':     os.getenv('repo.%s.warning' % code, '10'),
149
        'critical':    os.getenv('repo.%s.critical' % code, '100')
150
    }
151
152
153
def print_config():
154
    print('graph_title Git repositories - Commits behind')
155
156
    print('graph_args --base 1000 -r --lower-limit 0')
157
    print('graph_vlabel number of commits behind')
158
    print('graph_scale yes')
159
    print('graph_info This graph shows the number of commits behind' +
160
          ' for each configured git repository')
161 eb9681a7 Neraud
    print('graph_category file_transfer')
162 a26b9e8d Neraud
163
    print('graph_order %s' % ' '.join(repo_codes))
164
165
    for repo_code in repos_conf.keys():
166
        print('%s.label %s' % (repo_code, repos_conf[repo_code]['name']))
167
        print('%s.warning %s' % (repo_code, repos_conf[repo_code]['warning']))
168
        print('%s.critical %s' %
169
              (repo_code, repos_conf[repo_code]['critical']))
170
171
172
def generate_git_command(repo_conf, git_command):
173
    if not repo_conf['user'] or repo_conf['user'] == os.environ['USER']:
174 c169373f Neraud
        cmd = [quote(conf['git_path'])] + git_command
175 a26b9e8d Neraud
    else:
176
        shell_cmd = 'cd %s ; %s %s' % (
177 c169373f Neraud
            quote(repo_conf['path']),
178
            quote(conf['git_path']),
179
            ' '.join(git_command))
180 a26b9e8d Neraud
        cmd = ['su', '-', repo_conf['user'], '-c', shell_cmd]
181
    return cmd
182
183
184
def execute_git_command(repo_conf, git_command):
185
    cmd = generate_git_command(repo_conf, git_command)
186
    return check_output(cmd, cwd=repo_conf['path']).decode('utf-8').rstrip()
187
188
189 81b129dd Neraud
def print_info():
190 a26b9e8d Neraud
    if not os.access(conf['git_path'], os.X_OK):
191
        print('Git (%s) is missing, or not executable !' %
192
              conf['git_path'], file=sys.stderr)
193
        sys.exit(1)
194
195
    for repo_code in repos_conf.keys():
196
        logging.debug(' - %s' % repo_code)
197
        try:
198
            remote_branch = execute_git_command(
199
                repos_conf[repo_code],
200
                ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'])
201
            logging.debug('remote_branch = %s' % remote_branch)
202
203
            commits_behind = execute_git_command(
204
                repos_conf[repo_code],
205
                ['rev-list', 'HEAD..%s' % remote_branch, '--count'])
206
207
            print('%s.value %d' % (repo_code, int(commits_behind)))
208
        except CalledProcessError as e:
209 397052a8 Neraud
            logging.error('Error executing git command : %s', e)
210 a26b9e8d Neraud
        except FileNotFoundError as e:
211
            logging.error('Repo not found at path %s' %
212
                          repos_conf[repo_code]['path'])
213
214
215 d079f0fa Neraud
def check_update_repos(mode):
216 61241b80 Neraud
    if not conf['state_file']:
217
        logging.error('Munin state file unavailable')
218
        sys.exit(1)
219
220 d079f0fa Neraud
    if mode != conf['update_mode']:
221
        logging.debug('Wrong mode, skipping')
222
        return
223 a26b9e8d Neraud
224
    if not os.path.isfile(conf['state_file']):
225
        logging.debug('No state file -> updating')
226
        do_update_repos()
227 d079f0fa Neraud
    elif (os.path.getmtime(conf['state_file']) + conf['update_maxinterval']
228
            < time.time()):
229 a26b9e8d Neraud
        logging.debug('State file last modified too long ago -> updating')
230
        do_update_repos()
231 d079f0fa Neraud
    elif randint(1, conf['update_probability']) == 1:
232 a26b9e8d Neraud
        logging.debug('Recent state, but random matched -> updating')
233
        do_update_repos()
234
    else:
235
        logging.debug('Recent state and random missed -> skipping')
236
237
238
def do_update_repos():
239
    for repo_code in repos_conf.keys():
240
        try:
241
            logging.info('Fetching repo %s' % repo_code)
242
            execute_git_command(repos_conf[repo_code], ['fetch'])
243
        except CalledProcessError as e:
244 397052a8 Neraud
            logging.error('Error executing git command : %s', e)
245 a26b9e8d Neraud
        except FileNotFoundError as e:
246
            logging.error('Repo not found at path %s' %
247
                          repos_conf[repo_code]['path'])
248
    logging.debug('Updating the state file')
249 e0b243ba Neraud
250
    # 'touch' the state file to update its last modified date
251
    Path(conf['state_file']).touch()
252 a26b9e8d Neraud
253
254
if len(sys.argv) > 1:
255
    action = sys.argv[1]
256
    if action == 'config':
257
        print_config()
258
    elif action == 'autoconf':
259 61241b80 Neraud
        errors = []
260
261
        if not conf['state_file']:
262
            errors.append('munin state file unavailable')
263
264 a26b9e8d Neraud
        if os.access(conf['git_path'], os.X_OK):
265
            test_git = call([conf['git_path'], '--version'], stdout=DEVNULL)
266 61241b80 Neraud
            if test_git != 0:
267
                errors.append('git seems to be broken ?!')
268
        else:
269
            errors.append('git is missing or not executable')
270
271
        if errors:
272
            print('no (%s)' % ', '.join(errors))
273 a26b9e8d Neraud
        else:
274 61241b80 Neraud
            print('yes')
275 a26b9e8d Neraud
    elif action == 'version':
276
        print('Git commit behind Munin plugin, version {0}'.format(
277
            plugin_version))
278
    elif action == 'update':
279 d079f0fa Neraud
        check_update_repos('cron')
280
    else:
281 397052a8 Neraud
        logging.warn("Unknown argument '%s'" % action)
282 a26b9e8d Neraud
        sys.exit(1)
283
else:
284 d079f0fa Neraud
    if conf['update_mode'] == 'munin':
285
        check_update_repos('munin')
286 81b129dd Neraud
    print_info()