Add new conditions: matches and contains

Both check a value against a regular expression. The former requires
full match, while the latter requires matching anywhere in the string
representaion of value.

Change-Id: Ia59d17d6f8383aed97696d678fc1e7e329242692
This commit is contained in:
Dmitry Tantsur 2016-01-26 17:01:35 +01:00
parent b7c61dee01
commit d6ff54faf7
6 changed files with 62 additions and 1 deletions

View File

@ -63,7 +63,9 @@ A condition is represented by an object with fields:
``op`` the type of comparison operation, default available operators include :
``eq``, ``le``, ``ge``, ``ne``, ``lt``, ``gt`` (basic comparison operators),
``in-net`` (checks that IP address is in a given network).
``in-net`` (checks that IP address is in a given network), ``contains``,
``matches`` (check field against a regular expression, matches require a full
match).
``field`` a `JSON path <http://goessner.net/articles/JsonPath/>`_ to the field
in the introspection data to use in comparison.

View File

@ -14,9 +14,11 @@
"""Standard plugins for rules API."""
import operator
import re
import netaddr
from ironic_inspector.common.i18n import _
from ironic_inspector.plugins import base
from ironic_inspector import utils
@ -79,6 +81,27 @@ class NetCondition(base.RuleConditionPlugin):
return netaddr.IPAddress(field) in network
class ReCondition(base.RuleConditionPlugin):
def validate(self, params, **kwargs):
try:
re.compile(params['value'])
except re.error as exc:
raise ValueError(_('invalid regular expression: %s') % exc)
class MatchesCondition(ReCondition):
def check(self, node_info, field, params, **kwargs):
regexp = params['value']
if regexp[-1] != '$':
regexp += '$'
return re.match(regexp, str(field)) is not None
class ContainsCondition(ReCondition):
def check(self, node_info, field, params, **kwargs):
return re.search(params['value'], str(field)) is not None
class FailAction(base.RuleActionPlugin):
REQUIRED_PARAMS = {'message'}

View File

@ -303,6 +303,8 @@ class Test(Base):
{'field': 'memory_mb', 'op': 'eq', 'value': 12288},
{'field': 'local_gb', 'op': 'gt', 'value': 998},
{'field': 'local_gb', 'op': 'lt', 'value': 1000},
{'field': 'local_gb', 'op': 'matches', 'value': '[0-9]+'},
{'field': 'cpu_arch', 'op': 'contains', 'value': '[0-9]+'},
],
'actions': [
{'action': 'set-attribute', 'path': '/extra/foo',

View File

@ -73,6 +73,34 @@ class TestSimpleConditions(test_base.BaseTest):
self._test(cond, expected, *values)
class TestReConditions(test_base.BaseTest):
def test_validate(self):
for cond in (rules_plugins.MatchesCondition(),
rules_plugins.ContainsCondition()):
cond.validate({'value': r'[a-z]?(foo|b.r).+'})
self.assertRaises(ValueError, cond.validate,
{'value': '**'})
def test_matches(self):
cond = rules_plugins.MatchesCondition()
for reg, field, res in [(r'.*', 'foo', True),
(r'fo{1,2}', 'foo', True),
(r'o{1,2}', 'foo', False),
(r'[1-9]*', 42, True),
(r'^(foo|bar)$', 'foo', True),
(r'fo', 'foo', False)]:
self.assertEqual(res, cond.check(None, field, {'value': reg}))
def test_contains(self):
cond = rules_plugins.ContainsCondition()
for reg, field, res in [(r'.*', 'foo', True),
(r'fo{1,2}', 'foo', True),
(r'o{1,2}', 'foo', True),
(r'[1-9]*', 42, True),
(r'bar', 'foo', False)]:
self.assertEqual(res, cond.check(None, field, {'value': reg}))
class TestNetCondition(test_base.BaseTest):
cond = rules_plugins.NetCondition()

View File

@ -0,0 +1,4 @@
---
features:
- New condition plugins "contains" and "matches" allow to match value against
regular expressions.

View File

@ -43,6 +43,8 @@ ironic_inspector.rules.conditions =
ge = ironic_inspector.plugins.rules:GeCondition
ne = ironic_inspector.plugins.rules:NeCondition
in-net = ironic_inspector.plugins.rules:NetCondition
matches = ironic_inspector.plugins.rules:MatchesCondition
contains = ironic_inspector.plugins.rules:ContainsCondition
ironic_inspector.rules.actions =
example = ironic_inspector.plugins.example:ExampleRuleAction
fail = ironic_inspector.plugins.rules:FailAction