diff --git a/insights/combiners/logrotate_conf.py b/insights/combiners/logrotate_conf.py index b786c2ddd4..496ae14a29 100644 --- a/insights/combiners/logrotate_conf.py +++ b/insights/combiners/logrotate_conf.py @@ -9,20 +9,14 @@ options, and all other options (if there are) will be discarded. """ - import operator import os -import string + from fnmatch import fnmatch -from insights.core import ConfigCombiner, ConfigParser -from insights.core.plugins import combiner, parser -from insights.parsers.logrotate_conf import LogrotateConf +from insights.core import ConfigCombiner +from insights.core.plugins import combiner +from insights.parsers.logrotate_conf import LogrotateConf, LogRotateConfPEG from insights.parsr.query import eq -from insights.specs import Specs -from insights.parsr import (AnyChar, Choice, EOF, EOL, Forward, LeftCurly, - LineEnd, Literal, Many, Number, OneLineComment, Opt, PosMarker, - QuotedString, RightCurly, skip_none, String, WS, WSChar) -from insights.parsr.query import Directive, Entry, Section @combiner(LogrotateConf) @@ -146,65 +140,7 @@ def configfile_of_logfile(self, log_file): return f -class DocParser(object): - def __init__(self, ctx): - self.ctx = ctx - - scripts = set("postrotate prerotate firstaction lastaction preremove".split()) - Stanza = Forward() - Spaces = Many(WSChar) - Bare = String(set(string.printable) - (set(string.whitespace) | set("#{}'\""))) - Num = Number & (WSChar | LineEnd) - Comment = OneLineComment("#").map(lambda x: None) - ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS - ScriptEnd = Literal("endscript") - Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x)) - Lines = Line.until(ScriptEnd | EOF).map(lambda x: "\n".join(x)) - Script = ScriptStart + Lines << Opt(ScriptEnd) - Script = Script.map(lambda x: [x[0], [x[1]], None]) - BeginBlock = WS >> LeftCurly << WS - EndBlock = WS >> RightCurly - First = PosMarker((Bare | QuotedString)) << Spaces - Attr = Spaces >> (Num | Bare | QuotedString) << Spaces - Rest = Many(Attr) - Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock - Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS - Stanza <= WS >> (Stmt | Comment) << WS - Doc = Many(Stanza).map(skip_none).map(self.to_entries) - - self.Top = Doc << EOF - - def to_entries(self, x): - ret = [] - for i in x: - name, attrs, body = i - if body: - for n in [name.value] + attrs: - ret.append(Section(name=n, children=body, lineno=name.lineno)) - else: - ret.append(Directive(name=name.value, attrs=attrs, lineno=name.lineno)) - return ret - - def __call__(self, content): - return self.Top(content) - - -def parse_doc(content, ctx=None): - """ Parse a configuration document into a tree that can be queried. """ - if isinstance(content, list): - content = "\n".join(content) - parse = DocParser(ctx) - result = parse(content) - return Entry(children=result, src=ctx) - - -@parser(Specs.logrotate_conf, continue_on_error=False) -class _LogRotateConf(ConfigParser): - def parse_doc(self, content): - return parse_doc("\n".join(content), ctx=self) - - -@combiner(_LogRotateConf) +@combiner(LogRotateConfPEG) class LogRotateConfTree(ConfigCombiner): """ Exposes logrotate configuration through the parsr query interface. @@ -228,12 +164,3 @@ def find_matches(self, confs, pattern): elif fnmatch(c.file_path, pattern): results.append(c) return sorted(results, key=operator.attrgetter("file_name")) - - -def get_tree(root=None): - """ - This is a helper function to get a logrotate configuration component for - your local machine or an archive. It's for use in interactive sessions. - """ - from insights import run - return run(LogRotateConfTree, root=root).get(LogRotateConfTree) diff --git a/insights/combiners/tests/test_logrotate_conf_tree.py b/insights/combiners/tests/test_logrotate_conf_tree.py index 212c1aa1ef..876a5c3d2b 100644 --- a/insights/combiners/tests/test_logrotate_conf_tree.py +++ b/insights/combiners/tests/test_logrotate_conf_tree.py @@ -1,8 +1,9 @@ # coding=utf-8 import pytest +from insights.combiners.logrotate_conf import LogRotateConfTree +from insights.parsers import logrotate_conf from insights.parsr.query import first -from insights.combiners.logrotate_conf import _LogRotateConf, LogRotateConfTree from insights.tests import context_wrap @@ -85,7 +86,7 @@ def test_logrotate_tree(): - p = _LogRotateConf(context_wrap(CONF, path="/etc/logrotate.conf")) + p = logrotate_conf.LogRotateConfPEG(context_wrap(CONF, path="/etc/logrotate.conf")) conf = LogRotateConfTree([p]) assert len(conf["weekly"]) == 1 assert len(conf["/var/log/wtmp"]["missingok"]) == 1 @@ -97,12 +98,11 @@ def test_logrotate_tree(): def test_junk_space(): - p = _LogRotateConf(context_wrap(JUNK_SPACE, path="/etc/logrotate.conf")) + p = logrotate_conf.LogRotateConfPEG(context_wrap(JUNK_SPACE, path="/etc/logrotate.conf")) conf = LogRotateConfTree([p]) assert "compress" in conf["/var/log/spooler"] def test_logrotate_conf_combiner_missing_endscript(): with pytest.raises(Exception): - p = _LogRotateConf(context_wrap(LOGROTATE_MISSING_ENDSCRIPT, path='/etc/logrotate.conf')), - print(p) + logrotate_conf.LogRotateConfPEG(context_wrap(LOGROTATE_MISSING_ENDSCRIPT, path='/etc/logrotate.conf')), diff --git a/insights/parsers/logrotate_conf.py b/insights/parsers/logrotate_conf.py index a970c324a3..8bb6adef37 100644 --- a/insights/parsers/logrotate_conf.py +++ b/insights/parsers/logrotate_conf.py @@ -7,11 +7,16 @@ - ``/etc/logrotate.d/*`` See: http://www.linuxmanpages.org/8/logrotate - """ +import string - -from .. import parser, Parser, get_active_lines, LegacyItemAccess +from insights.core import ConfigParser, LegacyItemAccess, Parser +from insights.core.plugins import parser +from insights.parsers import ParseException, get_active_lines +from insights.parsr import (AnyChar, Choice, EOF, EOL, Forward, LeftCurly, LineEnd, + Literal, Many, Number, OneLineComment, Opt, PosMarker, + QuotedString, RightCurly, skip_none, String, WS, WSChar) +from insights.parsr.query import Directive, Entry, Section from insights.specs import Specs PAIRED_OPTS = ( @@ -143,3 +148,110 @@ def _parse_opts(line): # common options in log_file section key, val = _parse_opts(line) log_opts[key] = val + + +class DocParser(object): + def __init__(self, ctx): + self.ctx = ctx + + scripts = set("postrotate prerotate firstaction lastaction preremove".split()) + Stanza = Forward() + Spaces = Many(WSChar) + Bare = String(set(string.printable) - (set(string.whitespace) | set("#{}'\""))) + Num = Number & (WSChar | LineEnd) + Comment = OneLineComment("#").map(lambda x: None) + ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS + ScriptEnd = Literal("endscript") + Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x)) + Lines = Line.until(ScriptEnd | EOF).map(lambda x: "\n".join(x)) + Script = ScriptStart + Lines << Opt(ScriptEnd) + Script = Script.map(lambda x: [x[0], [x[1]], None]) + BeginBlock = WS >> LeftCurly << WS + EndBlock = WS >> RightCurly + First = PosMarker((Bare | QuotedString)) << Spaces + Attr = Spaces >> (Num | Bare | QuotedString) << Spaces + Rest = Many(Attr) + Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock + Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS + Stanza <= WS >> (Stmt | Comment) << WS + Doc = Many(Stanza).map(skip_none).map(self.to_entries) + + self.Top = Doc << EOF + + def to_entries(self, x): + ret = [] + for i in x: + name, attrs, body = i + if body: + for n in [name.value] + attrs: + ret.append(Section(name=n, children=body, lineno=name.lineno)) + else: + ret.append(Directive(name=name.value, attrs=attrs, lineno=name.lineno)) + return ret + + def __call__(self, content): + try: + return self.Top(content) + except Exception: + raise ParseException("There was an exception when parsing the logrotate config file.") + + +def parse_doc(content, ctx=None): + """ Parse a configuration document into a tree that can be queried. """ + if isinstance(content, list): + content = "\n".join(content) + parse = DocParser(ctx) + result = parse(content) + return Entry(children=result, src=ctx) + + +@parser(Specs.logrotate_conf) +class LogRotateConfPEG(ConfigParser): + """ + Sample logrotate configuration file:: + + # sample file + compress + + /var/log/messages { + rotate 5 + weekly + postrotate + /sbin/killall -HUP syslogd + endscript + } + + "/var/log/httpd/access.log" /var/log/httpd/error.log { + rotate 5 + mail www@my.org + size=100k + sharedscripts + postrotate + /sbin/killall -HUP httpd + endscript + } + + /var/log/news/news.crit + /var/log/news/olds.crit { + monthly + rotate 2 + olddir /var/log/news/old + missingok + postrotate + kill -HUP `cat /var/run/inn.pid` + endscript + nocompress + } + + Examples: + >>> type(log_rt_peg) + + >>> 'compress' in log_rt_peg + True + >>> 'weekly' in log_rt['/var/log/messages'] + True + >>> log_rt_peg['/var/log/messages']['postrotate'][-1].value + '/sbin/killall -HUP syslogd' + """ + def parse_doc(self, content): + return parse_doc("\n".join(content), ctx=self) diff --git a/insights/parsers/tests/test_logrotate_conf.py b/insights/parsers/tests/test_logrotate_conf.py index ca6593e936..50ac00a4f0 100644 --- a/insights/parsers/tests/test_logrotate_conf.py +++ b/insights/parsers/tests/test_logrotate_conf.py @@ -124,8 +124,9 @@ def test_web_xml_doc_examples(): env = { - 'log_rt': LogrotateConf(context_wrap(LOGROTATE_MAN_PAGE_DOC, path='/etc/logrotate.conf')), - } + 'log_rt': logrotate_conf.LogrotateConf(context_wrap(LOGROTATE_MAN_PAGE_DOC, path='/etc/logrotate.conf')), + 'log_rt_peg': logrotate_conf.LogRotateConfPEG(context_wrap(LOGROTATE_MAN_PAGE_DOC, path='/etc/logrotate.conf')), + } failed, total = doctest.testmod(logrotate_conf, globs=env) assert failed == 0