Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Move _LogRotateConf parser out of combiner #3389

Merged
merged 1 commit into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 5 additions & 78 deletions insights/combiners/logrotate_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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)
10 changes: 5 additions & 5 deletions insights/combiners/tests/test_logrotate_conf_tree.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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')),
118 changes: 115 additions & 3 deletions insights/parsers/logrotate_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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 [email protected]
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)
<class 'insights.parsers.logrotate_conf.LogRotateConfPEG'>
>>> '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)
5 changes: 3 additions & 2 deletions insights/parsers/tests/test_logrotate_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down