Projet

Général

Profil

Révision a26b9e8d

IDa26b9e8db6b07ed7749b67dd3c5dcd006db6c10e
Parent b35840c5
Enfant 397052a8

Ajouté par Neraud il y a plus de 7 ans

Added git_commit_behind

Voir les différences:

plugins/git/git_commit_behind
1
#! /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
To avoid fetching all repos every 5 minutes and slowing down the munin-node,
15
the fetch operation is triggered via a cron job.
16

  
17
=head1 REQUIREMENTS
18

  
19
 - Python3
20
 - Git
21

  
22
=head1 INSTALLATION
23

  
24
Link this plugin, as usual.
25
For example :
26
  ln -s /path/to/git_commit_behind /etc/munin/plugins/git_commit_behind
27

  
28
You also need to setup a cron job to trigger the git fetches.
29

  
30
The plugin can be called with an "update" mode to handle the fetches :
31
munin-run git_commit_behind update <maxinterval> <probability>
32
It will run the fetches randomly (1 in <probability> chances),
33
and ensure that it is run at least every <maxinterval> seconds.
34

  
35
For example, you can use the following cron :
36

  
37
# If the git_commit_behind plugin is enabled, fetch git repositories approx.
38
# once an hour (12 invocations an hour, 1 in 12 chance that the update will
39
# happen), but ensure that there will never be more than two hours
40
# (7200 seconds) interval between updates.
41
*/5 * * * * root if [ -x /etc/munin/plugins/git_commit_behind ]; then /usr/sbin/munin-run git_commit_behind update 7200 12 >/dev/null; fi
42

  
43
=head1 CONFIGURATION
44

  
45
Use your "/etc/munin/plugin-conf.d/munin-node" to configure this plugin.
46
    [git_commit_behind]
47
    user root
48
    env.git_path /path/to/git
49

  
50
Then, for each repository you want to check, you need the following
51
configuration block under the git_commit_behind section
52
    env.repo.[repoCode].path /path/to/local/repo
53
    env.repo.[repoCode].name Repo Name
54
    env.repo.[repoCode].user user
55
    env.repo.[repoCode].warning 10
56
    env.repo.[repoCode].critical 100
57

  
58
[repoCode] can only contain letters, numbers and underscores.
59

  
60
path : mandatory, the local path to your git repository
61
name : optional (default : [repoCode]), a cleaner name that will be displayed
62
user : optional (default : empty), the owner of the repository
63
       if set and different from the user running the plugin, the git commands
64
       will be executed as this user
65
warning : optional (default 10), the warning threshold
66
critical : optional (default 100), the critical threshold
67

  
68
For example :
69

  
70
    [git_commit_behind]
71
    user root
72

  
73
    env.repo.munin_contrib.path /opt/munin-contrib
74
    env.repo.munin_contrib.name Munin Contrib
75

  
76
    env.repo.other_repo.path /path/to/other-repo
77
    env.repo.other_repo.name Other Repo
78

  
79
=head1 MAGIC MARKERS
80

  
81
  #%# family=auto
82
  #%# capabilities=autoconf
83

  
84
=head1 VERSION
85

  
86
1.0.0
87

  
88
=head1 AUTHOR
89

  
90
Neraud (https://github.com/Neraud)
91

  
92
=head1 LICENSE
93

  
94
GPLv2
95

  
96
=cut"""
97

  
98

  
99
from __future__ import print_function
100

  
101
import logging
102
import os
103
from random import randint
104
import re
105
from subprocess import check_output, call, DEVNULL, CalledProcessError
106
import sys
107
import time
108

  
109

  
110
plugin_version = "1.0.0"
111

  
112
debug = int(os.getenv('MUNIN_DEBUG', os.getenv('DEBUG', 0))) > 0
113
if debug:
114
    logging.basicConfig(level=logging.DEBUG,
115
                        format='%(asctime)s %(levelname)-7s %(message)s')
116

  
117
conf = {
118
    'git_path':  os.getenv('git_path', '/usr/bin/git'),
119
    'state_file': os.getenv('MUNIN_STATEFILE',
120
                            '/var/lib/munin-node/plugin-state/nobody/' +
121
                            'git_commit_behind.state')
122
}
123

  
124
repo_codes = set([re.search('repo\.([^.]+)\..*', elem).group(1)
125
                  for elem in os.environ.keys() if 'repo.' in elem])
126

  
127
repos_conf = {}
128
for code in repo_codes:
129
    repos_conf[code] = {
130
        'name':        os.getenv('repo.%s.name' % code, code),
131
        'path':        os.getenv('repo.%s.path' % code, None),
132
        'user':        os.getenv('repo.%s.user' % code, None),
133
        'warning':     os.getenv('repo.%s.warning' % code, '10'),
134
        'critical':    os.getenv('repo.%s.critical' % code, '100')
135
    }
136

  
137

  
138
def print_config():
139
    print('graph_title Git repositories - Commits behind')
140

  
141
    print('graph_args --base 1000 -r --lower-limit 0')
142
    print('graph_vlabel number of commits behind')
143
    print('graph_scale yes')
144
    print('graph_info This graph shows the number of commits behind' +
145
          ' for each configured git repository')
146
    print('graph_category system')
147

  
148
    print('graph_order %s' % ' '.join(repo_codes))
149

  
150
    for repo_code in repos_conf.keys():
151
        print('%s.label %s' % (repo_code, repos_conf[repo_code]['name']))
152
        print('%s.warning %s' % (repo_code, repos_conf[repo_code]['warning']))
153
        print('%s.critical %s' %
154
              (repo_code, repos_conf[repo_code]['critical']))
155

  
156

  
157
def generate_git_command(repo_conf, git_command):
158
    if not repo_conf['user'] or repo_conf['user'] == os.environ['USER']:
159
        cmd = [conf['git_path']] + git_command
160
    else:
161
        shell_cmd = 'cd %s ; %s %s' % (
162
            repo_conf['path'], conf['git_path'], ' '.join(git_command))
163
        cmd = ['su', '-', repo_conf['user'], '-c', shell_cmd]
164
    return cmd
165

  
166

  
167
def execute_git_command(repo_conf, git_command):
168
    cmd = generate_git_command(repo_conf, git_command)
169
    return check_output(cmd, cwd=repo_conf['path']).decode('utf-8').rstrip()
170

  
171

  
172
def get_info():
173
    if not os.access(conf['git_path'], os.X_OK):
174
        print('Git (%s) is missing, or not executable !' %
175
              conf['git_path'], file=sys.stderr)
176
        sys.exit(1)
177

  
178
    for repo_code in repos_conf.keys():
179
        logging.debug(' - %s' % repo_code)
180
        try:
181
            remote_branch = execute_git_command(
182
                repos_conf[repo_code],
183
                ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'])
184
            logging.debug('remote_branch = %s' % remote_branch)
185

  
186
            commits_behind = execute_git_command(
187
                repos_conf[repo_code],
188
                ['rev-list', 'HEAD..%s' % remote_branch, '--count'])
189

  
190
            print('%s.value %d' % (repo_code, int(commits_behind)))
191
        except CalledProcessError as e:
192
            logging.error('Error executing git command : %s' % str(e))
193
        except FileNotFoundError as e:
194
            logging.error('Repo not found at path %s' %
195
                          repos_conf[repo_code]['path'])
196

  
197

  
198
def check_update_repos():
199
    if len(sys.argv) > 2:
200
        max_interval = int(sys.argv[2])
201
    else:
202
        max_interval = 7200
203

  
204
    if len(sys.argv) > 3:
205
        probability = int(sys.argv[3])
206
    else:
207
        probability = 12
208

  
209
    if not os.path.isfile(conf['state_file']):
210
        logging.debug('No state file -> updating')
211
        do_update_repos()
212
    elif os.path.getmtime(conf['state_file']) + max_interval < time.time():
213
        logging.debug('State file last modified too long ago -> updating')
214
        do_update_repos()
215
    elif randint(1, probability) == 1:
216
        logging.debug('Recent state, but random matched -> updating')
217
        do_update_repos()
218
    else:
219
        logging.debug('Recent state and random missed -> skipping')
220

  
221

  
222
def do_update_repos():
223
    for repo_code in repos_conf.keys():
224
        try:
225
            logging.info('Fetching repo %s' % repo_code)
226
            execute_git_command(repos_conf[repo_code], ['fetch'])
227
        except CalledProcessError as e:
228
            logging.error('Error executing git command : %s' % str(e))
229
        except FileNotFoundError as e:
230
            logging.error('Repo not found at path %s' %
231
                          repos_conf[repo_code]['path'])
232
    logging.debug('Updating the state file')
233
    open(conf['state_file'], 'w')
234

  
235

  
236
if len(sys.argv) > 1:
237
    action = sys.argv[1]
238
    if action == 'config':
239
        print_config()
240
    elif action == 'autoconf':
241
        if os.access(conf['git_path'], os.X_OK):
242
            test_git = call([conf['git_path'], '--version'], stdout=DEVNULL)
243
            if test_git == 0:
244
                print('yes')
245
            else:
246
                print('no (git seems to be broken ?!)')
247
        else:
248
            print('no (git is missing or not executable)')
249
    elif action == 'version':
250
        print('Git commit behind Munin plugin, version {0}'.format(
251
            plugin_version))
252
    elif action == 'update':
253
        check_update_repos()
254
    elif action:
255
        logging.warn('Unknown argument \'%s\'' % action)
256
        sys.exit(1)
257
    else:
258
        get_info()
259
else:
260
    get_info()

Formats disponibles : Unified diff