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
|
# this script generates for each plugin:
# - its name
# - URL to upstream code
# - list of distros
# - list of profiles
# - list of packages that enable the plugin (no other enabling pieces)
# - list of paths it collects (add_copy_spec)
# - list of paths it forbits to collect (add_forbidden_path)
# - list of commands it calls (add_cmd_output)
#
# Output of the script:
# - a JSON object with plugins in keys
# - or CSV format in case "csv" cmdline is provided
#
# TODO:
# - improve parsing that will be never ideal :)
# - add other methods:
# - add_blockdev_cmd
# - add_string_as_file
# - ??
import os
import re
import json
import sys
PLUGDIR = 'sos/report/plugins'
plugs_data = {} # the map of all plugins data to collect
plugcontent = '' # content of plugin file just being processed
# method to parse an item of a_s_c/a_c_o/.. methods
# we work on an assumption the item is a string quoted by \" or optionally
# by \'. If we detect at least 2 such chars in the item, take what is between
# those.
def add_valid_item(dest, item):
for qoutemark in "\"\'":
split = item.split(qoutemark)
if len(split) > 2:
dest.append(split[1])
return
# method to find in `plugcontent` all items of given method (a_c_s/a_c_o/..)
# split by comma; add each valid item to the `dest` list
def add_all_items(method, dest, wrapopen=r'\(', wrapclose=r'\)'):
regexp = f"{method}{wrapopen}(.*?){wrapclose}"
for match in re.findall(regexp, plugcontent, flags=re.MULTILINE | re.DOTALL):
# tuple of distros ended by either (class|from|import)
if isinstance(match, tuple):
for item in list(match):
if item not in ['class', 'from', 'import']:
for it in item.split(','):
# dirty hack to remove spaces and "Plugin"
if "Plugin" not in it:
continue
it = it.strip(' ()')[0:-6]
if len(it):
dest.append(it)
# list of specs separated by comma ..
elif match.startswith('[') or match.startswith('('):
for item in match.split(','):
add_valid_item(dest, item)
# .. or a singleton spec
else:
add_valid_item(dest, match)
# main body: traverse report's plugins directory and for each plugin, grep for
# add_copy_spec / add_forbidden_path / add_cmd_output there
for plugfile in sorted(os.listdir(PLUGDIR)):
# ignore non-py files and __init__.py
if not plugfile.endswith('.py') or plugfile == '__init__.py':
continue
plugname = plugfile[:-3]
# if plugname != 'bcache':
# continue
plugs_data[plugname] = {
'sourcecode': 'https://github.com/sosreport/sos/blob/'
f'main/sos/report/plugins/{plugname}.py',
'distros': [],
'profiles': [],
'packages': [],
'copyspecs': [],
'forbidden': [],
'commands': [],
'service_status': [],
'journals': [],
'env': [],
}
plugcontent = open(
os.path.join(PLUGDIR, plugfile)).read().replace('\n', '')
add_all_items(
"from sos.report.plugins import ",
plugs_data[plugname]['distros'],
wrapopen='',
wrapclose='(class|from|import)'
)
add_all_items("profiles = ", plugs_data[plugname]['profiles'], wrapopen='')
add_all_items("packages = ", plugs_data[plugname]['packages'], wrapopen='')
add_all_items("add_copy_spec", plugs_data[plugname]['copyspecs'])
add_all_items("add_forbidden_path", plugs_data[plugname]['forbidden'])
add_all_items("add_cmd_output", plugs_data[plugname]['commands'])
add_all_items("collect_cmd_output", plugs_data[plugname]['commands'])
add_all_items("add_service_status", plugs_data[plugname]['service_status'])
add_all_items("add_journal", plugs_data[plugname]['journals'])
add_all_items("add_env_var", plugs_data[plugname]['env'])
# print output; if "csv" is cmdline argument, print in CSV format, else JSON
if (len(sys.argv) > 1) and (sys.argv[1] == "csv"):
print("plugin;url;distros;profiles;packages;copyspecs;forbidden;commands;"
"service_status;journals;env_vars")
for plugname in plugs_data.keys():
plugin = plugs_data[plugname]
# determine max number of lines - usually
# "max(len(copyspec),len(commands))"
# ignore 'sourcecode' key as it
maxline = 1
plugkeys = list(plugin.keys())
plugkeys.remove('sourcecode')
for key in plugkeys:
maxline = max(maxline, len(plugin[key]))
for line in range(maxline):
out = ";" if line > 0 else f"{plugname};{plugin['sourcecode']}"
for key in plugkeys:
out += ";"
if line < len(plugin[key]):
out += plugin[key][line]
print(out)
else:
print(json.dumps(plugs_data))
|