Skip to content

Commit b350e34

Browse files
committed
[global] Update config file location and layout
Moves the default config file we look for to /etc/sos/sos.conf instead of /etc/sos.conf. Extends the config file to look for a section matching the name of the component being used. Renames the "general" section to "global" and the "tunables" section to "plugin_options". Updates the default sos.conf to this style and adds some comments to the file. Update the man page for sos.conf. Note that this commit does NOT update sos.spec to drop the default configuration file in the new location, as that will be handled by a later commit to update the specfile wholesale. Closes: sosreport#2125 Resolves: sosreport#2136 Signed-off-by: Jake Hunsaker <[email protected]>
1 parent 063273c commit b350e34

File tree

6 files changed

+169
-99
lines changed

6 files changed

+169
-99
lines changed

man/en/sos.conf.5

+23-21
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
sos.conf \- sosreport configuration
44
.SH DESCRIPTION
55
.sp
6-
sosreport uses a configuration file at /etc/sos.conf.
6+
sosreport uses a configuration file at /etc/sos/sos.conf.
77
.SH PARAMETERS
88
.sp
9-
There are three sections in the sosreport configuration file:
10-
general, plugins and tunables. Options are set using 'ini'-style
9+
There are sections for each sos component, as well as global values and
10+
those for plugin options. Options are set using 'ini'-style
1111
\fBname = value\fP pairs. Disabling/enabling a boolean option
1212
is done the same way like on command line (e.g. process.lsof=off).
1313

@@ -18,30 +18,30 @@ will result in enabling those options, regardless of value set.
1818

1919
Sections are parsed in the ordering:
2020
.br
21-
- \fB[general]\fP
21+
- \fB[global]\fP
2222
.br
23-
- \fB[plugins]\fP (disable)
23+
- \fB[component]\fP
2424
.br
25-
- \fB[plugins]\fP (enable)
26-
.br
27-
- \fB[tunables]\fP
25+
- \fB[plugin_options]\fP
2826

2927
.TP
30-
\fB[general]\fP
28+
\fB[global]\fP
3129
<option> Sets (long) option value. Short options (i.e. z=auto)
3230
are not supported.
3331
.TP
34-
\fB[plugins]\fP
35-
disable Comma separated list of plugins to disable.
36-
.br
37-
enable Comma separated list of plugins to enable.
32+
\fB[component]\fP
33+
Each component will have a separate section, and it will support the options
34+
that particular component provides. These are readily identifiable in the
35+
\fB--help\fP output for each component, E.G. \fBsos report --help\fP.
3836
.TP
39-
\fB[tunables]\fP
40-
plugin.option Alter available options for defined plugin.
37+
\fB[plugin_options]\fP
38+
Alter available options for defined (and loaded) plugins.
39+
40+
Takes the form plugin.option = value, for example \fBrpm.rpmva = true\fP.
4141
.SH EXAMPLES
4242
To use quiet and batch mode with 10 threads:
4343
.LP
44-
[general]
44+
[global]
4545
.br
4646
batch=yes
4747
.br
@@ -51,19 +51,21 @@ threads=10
5151
.sp
5252
To disable the 'host' and 'filesys' plugins:
5353
.LP
54-
[plugins]
54+
[report]
5555
.br
56-
disable = host, filesys
56+
noplugins = host,filesys
5757
.sp
5858
To disable rpm package verification in the RPM plugin:
5959
.LP
60-
[tunables]
60+
[plugin_options]
6161
.br
6262
rpm.rpmva = off
6363
.br
6464
.SH FILES
6565
.sp
66-
/etc/sos.conf
66+
/etc/sos/sos.conf
6767
.SH SEE ALSO
6868
.sp
69-
sosreport(1)
69+
sos-report(1)
70+
sos-collect(1)
71+
sos-clean(1)

sos.conf

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
1-
[general]
1+
[global]
2+
# Set global options here that are not component specific
3+
# If you would like one global default value to be specifically overridden for
4+
# just one component, but not others, you may override that value in the
5+
# component specific section below
26
#verbose = 3
37
#verify = yes
48
#batch = yes
59
#log-size = 15
610

7-
[plugins]
8-
#disable = rpm, selinux, dovecot
11+
[report]
12+
# Options that will apply to any `sos report` run should be listed here.
13+
# Note that the option names *must* be the long-form name as seen in --help
14+
# output. Use a comma for list delimitations.
15+
#skip-plugins = rpm, selinux, dovecot
16+
#enable-plugins = host,logs
917

10-
[tunables]
18+
[collect]
19+
# Options that will apply to any `sos collect` run should be listed here.
20+
# Note that the option names *must* be the long-form name as seen in --help
21+
# output. Use a comma for list delimitations
22+
#master = myhost.example.com
23+
#ssh-key = /home/user/.ssh/mykey
24+
#password = true
25+
26+
[clean]
27+
# Options that will apply to any `sos clean|mask` run should be listed here.
28+
# Note that the option names *must* be the long-form name as seen in --help
29+
# output. Use a comma for list delimitations
30+
#domains = mydomain.com
31+
#no-update = true
32+
33+
[plugin_options]
34+
# Specify any plugin options and their values here. These options take the form
35+
# plugin_name.option_name = value
1136
#rpm.rpmva = off

sos/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ def _add_common_options(self, parser):
127127
global_grp.add_argument("--batch", default=False, action="store_true",
128128
help="Do not prompt interactively")
129129
global_grp.add_argument("--config-file", type=str, action="store",
130-
dest="config_file", default="/etc/sos.conf",
130+
dest="config_file",
131+
default="/etc/sos/sos.conf",
131132
help="specify alternate configuration file")
132133
global_grp.add_argument("--debug", action="store_true", dest="debug",
133134
help="enable interactive debugging using the "
@@ -173,6 +174,7 @@ def _init_component(self):
173174
if _to_load.root_required and not os.getuid() == 0:
174175
raise Exception("Component must be run with root privileges")
175176
self._component = _to_load(self.parser, self.args, self.cmdline)
177+
176178
except Exception as err:
177179
print("Could not initialize '%s': %s" % (_com, err))
178180
if self.args.debug:

sos/component.py

+42-6
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class SoSComponent():
5353
_arg_defaults = {
5454
"batch": False,
5555
"compression_type": 'auto',
56-
"config_file": '/etc/sos.conf',
56+
"config_file": '/etc/sos/sos.conf',
5757
"debug": False,
5858
"encrypt_key": None,
5959
"encrypt_pass": None,
@@ -158,6 +158,41 @@ def add_parser_options(cls, parser):
158158
"""
159159
pass
160160

161+
def apply_options_from_cmdline(self, opts):
162+
"""(Re-)apply options specified via the cmdline to an options instance
163+
164+
There are several cases where we may need to re-apply the options from
165+
the cmdline over previously loaded options - for instance when an
166+
option is specified in both a config file and cmdline, or a preset and
167+
the cmdline, or all three.
168+
169+
Use this to re-apply cmdline option overrides to anything that may
170+
change the default values of options
171+
172+
Positional arguments:
173+
174+
:param opts: SoSOptions object to update
175+
176+
"""
177+
# override the values from config files with the values from the
178+
# cmdline iff that value was explicitly specified, and compare it to
179+
# the _current_ set of opts from the config files as a default
180+
cmdopts = SoSOptions().from_args(
181+
self.parser.parse_args(self.cmdline),
182+
arg_defaults=opts.dict(preset_filter=False)
183+
)
184+
# we can't use merge() here, as argparse will pass default values for
185+
# unset options which would effectively negate config file settings and
186+
# set all values back to their normal default
187+
codict = cmdopts.dict(preset_filter=False)
188+
for opt, val in codict.items():
189+
if opt not in cmdopts.arg_defaults.keys():
190+
continue
191+
if val and val != opts.arg_defaults[opt]:
192+
setattr(opts, opt, val)
193+
194+
return opts
195+
161196
def load_options(self):
162197
"""Compile arguments loaded from defaults, config files, and the
163198
command line into a usable set of options
@@ -169,12 +204,13 @@ def load_options(self):
169204
if option.default != SUPPRESS:
170205
option.default = None
171206

172-
# load values from cmdline
173-
cmdopts = SoSOptions().from_args(self.parser.parse_args(self.cmdline))
174-
opts.merge(cmdopts)
207+
opts.update_from_conf(self.args.config_file, self.args.component)
208+
if os.getuid() != 0:
209+
userconf = os.path.join(Path.home(), '.config/sos/sos.conf')
210+
if os.path.exists(userconf):
211+
opts.update_from_conf(userconf, self.args.component)
175212

176-
# load values from config file
177-
opts.update_from_conf(opts.config_file)
213+
opts = self.apply_options_from_cmdline(opts)
178214

179215
return opts
180216

sos/options.py

+50-47
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
# See the LICENSE file in the source distribution for further information.
88

99
from argparse import Action
10-
from configparser import ConfigParser, ParsingError, Error
10+
from configparser import (ConfigParser, ParsingError, Error,
11+
DuplicateOptionError)
1112

1213

1314
def _is_seq(val):
@@ -106,15 +107,15 @@ def __init__(self, arg_defaults={}, **kwargs):
106107
setattr(self, arg, kwargs[arg])
107108

108109
@classmethod
109-
def from_args(cls, args):
110+
def from_args(cls, args, arg_defaults={}):
110111
"""Initialise a new SoSOptions object from a ``Namespace``
111112
obtained by parsing command line arguments.
112113
113114
:param args: parsed command line arguments
114115
:returns: an initialised SoSOptions object
115116
:returntype: SoSOptions
116117
"""
117-
opts = SoSOptions(**vars(args))
118+
opts = SoSOptions(**vars(args), arg_defaults=arg_defaults)
118119
opts._merge_opts(args, True)
119120
return opts
120121

@@ -169,58 +170,59 @@ def _convert_to_type(self, key, val, conf):
169170
% (key, conf))
170171
return val
171172

172-
def update_from_conf(self, config_file):
173+
def update_from_conf(self, config_file, component):
174+
"""Read the provided config_file and update options from that.
175+
176+
Positional arguments:
177+
178+
:param config_file: Filepath to the config file
179+
:param component: Which component (section) to load
180+
"""
181+
182+
def _update_from_section(section, config):
183+
if config.has_section(section):
184+
odict = dict(config.items(section))
185+
# handle verbose explicitly
186+
if 'verbose' in odict.keys():
187+
odict['verbosity'] = int(odict.pop('verbose'))
188+
# convert options names
189+
for key in odict.keys():
190+
if '-' in key:
191+
odict[key.replace('-', '_')] = odict.pop(key)
192+
# set the values according to the config file
193+
for key, val in odict.items():
194+
if isinstance(val, str):
195+
val = val.replace(' ', '')
196+
if key not in self.arg_defaults:
197+
# read an option that is not loaded by the current
198+
# SoSComponent
199+
print("Unknown option '%s' in section '%s'"
200+
% (key, section))
201+
continue
202+
val = self._convert_to_type(key, val, config_file)
203+
setattr(self, key, val)
204+
173205
config = ConfigParser()
174206
try:
175207
try:
176208
with open(config_file) as f:
177209
config.readfp(f)
178-
except (ParsingError, Error) as e:
210+
except DuplicateOptionError as err:
211+
raise exit("Duplicate option '%s' in section '%s' in file %s"
212+
% (err.option, err.section, config_file))
213+
except (ParsingError, Error):
179214
raise exit('Failed to parse configuration file %s'
180215
% config_file)
181216
except (OSError, IOError) as e:
182217
raise exit('Unable to read configuration file %s : %s'
183218
% (config_file, e.args[1]))
184219

185-
if config.has_section("general"):
186-
odict = dict(config.items("general"))
187-
# handle verbose explicitly
188-
if 'verbose' in odict.keys():
189-
odict['verbosity'] = int(odict.pop('verbose'))
190-
# convert options names
191-
for key in odict.keys():
192-
if '-' in key:
193-
odict[key.replace('-', '_')] = odict.pop(key)
194-
# set the values according to the config file
195-
for key, val in odict.items():
196-
if key not in self.arg_defaults:
197-
# read an option that is not loaded by the current
198-
# SoSComponent
199-
continue
200-
val = self._convert_to_type(key, val, config_file)
201-
setattr(self, key, val)
202-
203-
# report specific checks
204-
205-
if hasattr(self, 'noplugins'):
206-
if config.has_option("plugins", "disable"):
207-
self.noplugins.extend([
208-
plugin.strip() for plugin in
209-
config.get("plugins", "disable").split(',')
210-
])
211-
212-
if hasattr(self, 'enableplugins'):
213-
if config.has_option("plugins", "enable"):
214-
self.enableplugins.extend([
215-
plugin.strip() for plugin in
216-
config.get("plugins", "enable").split(',')
217-
])
218-
219-
if hasattr(self, 'plugopts'):
220-
if config.has_section("tunables"):
221-
for opt, val in config.items("tunables"):
222-
if not opt.split('.')[0] in opts.noplugins:
223-
self.plugopts.append(opt + "=" + val)
220+
_update_from_section("global", config)
221+
_update_from_section(component, config)
222+
if config.has_section("plugin_options") and hasattr(self, 'plugopts'):
223+
for key, val in config.items("plugin_options"):
224+
if not key.split('.')[0] in self.skip_plugins:
225+
self.plugopts.append(key + '=' + val)
224226

225227
def merge(self, src, skip_default=True):
226228
"""Merge another set of ``SoSOptions`` into this object.
@@ -237,7 +239,7 @@ def merge(self, src, skip_default=True):
237239
if getattr(src, arg) is not None or not skip_default:
238240
self._merge_opt(arg, src, False)
239241

240-
def dict(self):
242+
def dict(self, preset_filter=True):
241243
"""Return this ``SoSOptions`` option values as a dictionary of
242244
argument name to value mappings.
243245
@@ -247,8 +249,9 @@ def dict(self):
247249
for arg in self.arg_names:
248250
value = getattr(self, arg)
249251
# Do not attempt to store preset option values in presets
250-
if arg in ('add_preset', 'del_preset', 'desc', 'note'):
251-
value = None
252+
if preset_filter:
253+
if arg in ('add_preset', 'del_preset', 'desc', 'note'):
254+
value = None
252255
odict[arg] = value
253256
return odict
254257

0 commit comments

Comments
 (0)