summaryrefslogtreecommitdiffstats
path: root/src/sbin/bcfg2-reports
blob: 2a8447ae4a47d0a0f83414b04aa27cbc076c67f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#!/usr/bin/env python
"""Query reporting system for client status."""

import os
import sys
import datetime
from optparse import OptionParser, OptionGroup, make_option
from Bcfg2.Compat import ConfigParser

try:
    import Bcfg2.settings
except ConfigParser.NoSectionError:
    print("Your bcfg2.conf is currently missing the [database] section which "
          "is necessary for the reporting interface. Please see bcfg2.conf(5) "
          "for more details.")
    sys.exit(1)

project_directory = os.path.dirname(Bcfg2.settings.__file__)
project_name = os.path.basename(project_directory)
sys.path.append(os.path.join(project_directory, '..'))
project_module = __import__(project_name, '', '', [''])
sys.path.pop()
# Set DJANGO_SETTINGS_MODULE appropriately.
os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name

from Bcfg2.Reporting.models import (Client, BaseEntry)
from django import db

def hosts_by_entry_type(clients, etype, entryspec):
    result = []
    for entry in entryspec:
        for client in clients:
            items = getattr(client.current_interaction, etype)()
            for item in items:
                if (item.entry_type == entry[0] and
                    item.name == entry[1]):
                    result.append(client)
    return result

def print_fields(fields, client, fmt, extra=None):
    """
    Prints the fields specified in fields of client, max_name
    specifies the column width of the name column.
    """
    fdata = []
    if extra is None:
        extra = dict()
    for field in fields:
        if field == 'time':
            fdata.append(str(client.current_interaction.timestamp))
        elif field == 'state':
            if client.current_interaction.isclean():
                fdata.append("clean")
            else:
                fdata.append("dirty")
        elif field == 'total':
            fdata.append(client.current_interaction.total_count)
        elif field == 'good':
            fdata.append(client.current_interaction.good_count)
        elif field == 'modified':
            fdata.append(client.current_interaction.modified_count)
        elif field == 'extra':
            fdata.append(client.current_interaction.extra_count)
        elif field == 'bad':
            fdata.append(client.current_interaction.bad_count)
        else:
            try:
                fdata.append(getattr(client, field))
            except:
                fdata.append(extra.get(field, "N/A"))

    print(fmt % tuple(fdata))

def print_entries(interaction, etype):
    items = getattr(interaction, etype)()
    for item in items:
        print("%-70s %s" % (item.entry_type + ":" + item.name, etype))

def main():
    parser = OptionParser(usage="%prog [options] <mode> [arg]")

    # single host modes
    multimodes = []
    singlemodes = []
    multimodes.append(make_option("-b", "--bad", action="store_true",
                                  default=False,
                                  help="Show bad entries from HOST"))
    multimodes.append(make_option("-e", "--extra", action="store_true",
                                  default=False,
                                  help="Show extra entries from HOST"))
    multimodes.append(make_option("-m", "--modified", action="store_true",
                                  default=False,
                                  help="Show modified entries from HOST"))
    multimodes.append(make_option("-s", "--show", action="store_true",
                                  default=False,
                                  help="Equivalent to --bad --extra --modified"))
    singlemodes.append(make_option("-t", "--total", action="store_true",
                                   default=False,
                                   help="Show total number of managed and good "
                                   "entries from HOST"))
    singlemodes.append(make_option("-x", "--expire", action="store_true",
                                   default=False,
                                   help="Toggle expired/unexpired state of "
                                   "HOST"))
    hostmodes = \
        OptionGroup(parser, "Single-Host Modes",
                    "The following mode flags require a single HOST argument")
    hostmodes.add_options(multimodes)
    hostmodes.add_options(singlemodes)
    parser.add_option_group(hostmodes)

    # all host modes
    allhostmodes = OptionGroup(parser, "Host Selection Modes",
                               "The following mode flags require no arguments")
    allhostmodes.add_option("-a", "--all", action="store_true", default=False,
                            help="Show all hosts, including expired hosts")
    allhostmodes.add_option("-c", "--clean", action="store_true", default=False,
                            help="Show only clean hosts")
    allhostmodes.add_option("-d", "--dirty", action="store_true", default=False,
                            help="Show only dirty hosts")
    allhostmodes.add_option("--stale", action="store_true", default=False,
                            help="Show hosts that haven't run in the last 24 "
                            "hours")
    parser.add_option_group(allhostmodes)

    # entry modes
    entrymodes = \
        OptionGroup(parser, "Entry Modes",
                    "The following mode flags require either any number of "
                    "TYPE:NAME arguments describing entries, or the --file "
                    "option")
    entrymodes.add_option("--badentry", action="store_true", default=False,
                          help="Show hosts that have bad entries that match "
                          "the argument")
    entrymodes.add_option("--modifiedentry", action="store_true", default=False,
                          help="Show hosts that have modified entries that "
                          "match the argument")
    entrymodes.add_option("--extraentry", action="store_true", default=False,
                          help="Show hosts that have extra entries that match "
                          "the argument")
    entrymodes.add_option("--entrystatus", action="store_true", default=False,
                          help="Show the status of the named entry on all "
                          "hosts. Only supports a single entry.")
    parser.add_option_group(entrymodes)

    # entry options
    entryopts = OptionGroup(parser, "Entry Options",
                            "Options that can be used with entry modes")
    entryopts.add_option("--fields", metavar="FIELD,FIELD,...",
                         help="Only display the listed fields",
                         default='name,time,state')
    entryopts.add_option("--file", metavar="FILE",
                         help="Read TYPE:NAME pairs from the specified file "
                         "instead of the command line")
    parser.add_option_group(entryopts)

    options, args = parser.parse_args()

    # make sure we've specified exactly one mode
    mode_family = None
    mode = None
    for opt in allhostmodes.option_list + entrymodes.option_list + \
            singlemodes:
        if getattr(options, opt.dest):
            if mode is not None:
                parser.error("Only one mode can be specified; found %s and %s" %
                             (mode.get_opt_string(), opt.get_opt_string()))
            mode = opt
            mode_family = parser.get_option_group(opt.get_opt_string())

    # you can specify more than one of --bad, --extra, --modified, --show, so
    # consider single-host options separately
    if not mode_family:
        for opt in multimodes:
            if getattr(options, opt.dest):
                mode_family = parser.get_option_group(opt.get_opt_string())
                break

    if not mode_family:
        parser.error("You must specify a mode")

    if mode_family == hostmodes:
        try:
            cname = args.pop()
            client = Client.objects.select_related().get(name=cname)
        except IndexError:
            parser.error("%s require a single HOST argument" % hostmodes.title)
        except Client.DoesNotExist:
            print("No such host: %s" % cname)
            return 2

        if options.expire:
            if client.expiration == None:
                client.expiration = datetime.datetime.now()
                print("Host expired.")
            else:
                client.expiration = None
                print("Host un-expired.")
            client.save()
        elif options.total:
            managed = client.current_interaction.total_count
            good = client.current_interaction.good_count
            print("Total managed entries: %d (good: %d)" % (managed, good))
        elif mode_family == hostmodes:
            if options.bad or options.show:
                print_entries(client.current_interaction, "bad")

            if options.modified or options.show:
                print_entries(client.current_interaction, "modified")

            if options.extra or options.show:
                print_entries(client.current_interaction, "extra")
    else:
        clients = Client.objects.exclude(current_interaction__isnull=True)
        result = list()
        edata = dict()
        fields = options.fields.split(',')

        if mode_family == allhostmodes:
            if args:
                print("%s do not take any arguments, ignoring" %
                      allhostmodes.title)

            for client in clients:
                interaction = client.current_interaction
                if (options.all or
                    (options.stale and interaction.isstale()) or
                    (options.clean and interaction.isclean()) or
                    (options.dirty and not interaction.isclean())):
                    result.append(client)
        else:
            # entry query modes
            if options.file:
                try:
                    entries = [l.strip().split(":")
                               for l in open(options.file)]
                except IOError:
                    err = sys.exc_info()[1]
                    print("Cannot read entries from %s: %s" % (options.file,
                                                               err))
                    return 2
            elif args:
                entries = [a.split(":") for a in args]
            else:
                parser.error("%s require either a list of entries on the "
                             "command line or the --file options" %
                             mode_family.title)

            if options.badentry:
                result = hosts_by_entry_type(clients, "bad", entries)
            elif options.modifiedentry:
                result = hosts_by_entry_type(clients, "modified", entries)
            elif options.extraentry:
                result = hosts_by_entry_type(clients, "extra", entries)
            elif options.entrystatus:
                if 'state' in fields:
                    fields.remove('state')
                fields.append("entry state")

                try:
                    entry_cls = BaseEntry.entry_from_type(entries[0][0])
                except ValueError:
                    print("Unhandled/unkown type %s" % entries[0][0])
                    return 2

                # todo batch fetch this.  sqlite could break
                for client in clients:
                    ents = entry_cls.objects.filter(name=entries[0][1],
                            interaction=client.current_interaction)
                    if len(ents) == 0:
                        continue
                    edata[client] = {"entry state": ents[0].get_state_display(),
                                     "reason": ents[0]}
                    result.append(client)


        if 'name' not in fields:
            fields.insert(0, "name")
        if not result:
            print("No match found")
            return
        max_name = max(len(c.name) for c in result)
        ffmt = []
        for field in fields:
            if field == "name":
                ffmt.append("%%-%ds" % max_name)
            elif field == "time":
                ffmt.append("%-19s")
            else:
                ffmt.append("%%-%ds" % len(field))
        fmt = "  ".join(ffmt)
        print(fmt % tuple(f.title() for f in fields))
        for client in result:
            if not client.expiration:
                print_fields(fields, client, fmt,
                             extra=edata.get(client, None))
    db.close_connection()


if __name__ == "__main__":
    sys.exit(main())