From 56fa8aa39f0bb519351b7009b50046352a59b250 Mon Sep 17 00:00:00 2001 From: Yu Ming Zhu Date: Jul 07 2021 09:09:28 +0000 Subject: [PATCH 1/2] [policy]: new AdvancedMatchTest (adv_match) for matching nested dict fixes: #2938 --- diff --git a/koji/policy.py b/koji/policy.py index 729e02e..34cf308 100644 --- a/koji/policy.py +++ b/koji/policy.py @@ -141,6 +141,46 @@ class MatchTest(BaseSimpleTest): return False +class AdvancedMatchTest(MatchTest): + """Matches a field in the data against glob patterns + + The field could be the same as match test, + or a dot-spiited string which indicates key in a (nested) dict + + True if any of the expressions match, else False + This test can be used as-is, or it can be subclassed to + test a specific field + + Syntax: + adv_match field[.sub_field ...] pattern1 [pattern2 ...] + + """ + name = 'adv_match' + field = None + + def run(self, data): + args = self.str.split()[1:] + if self.field is None: + field = args[0] + args = args[1:] + else: + # expected when we are subclassed + field = self.field + fields = field.split('.') + tgt = data + for i, f in enumerate(fields): + if not isinstance(tgt, dict): + return False + if f not in tgt: + return False + tgt = tgt.get(f) + + for pattern in args: + if fnmatch.fnmatch(tgt, pattern): + return True + return False + + class TargetTest(MatchTest): """Matches target in the data against glob patterns diff --git a/tests/test_lib/test_policy.py b/tests/test_lib/test_policy.py index fe907fd..be2630b 100644 --- a/tests/test_lib/test_policy.py +++ b/tests/test_lib/test_policy.py @@ -66,6 +66,23 @@ class TestBasicTests(unittest.TestCase): self.assertTrue(obj.run({'thing': 'elseplus'})) self.assertFalse(obj.run({})) + def test_adv_match_test(self): + obj = koji.policy.AdvancedMatchTest('not_important foo *bar*') + self.assertTrue(obj.run({'foo': 'foobar'})) + self.assertFalse(obj.run({'foo': 'nah...'})) + obj = koji.policy.AdvancedMatchTest('not_important foo.bar.hello world?') + self.assertTrue(obj.run({'foo': { + 'bar': { + 'hello': 'world!' + } + }})) + self.assertFalse(obj.run({'foo': 'nah???'})) + self.assertFalse(obj.run({'foo': { + 'bar': { + 'hello': 'world!!' + } + }})) + def test_target_test(self): obj = koji.policy.TargetTest('target valid') self.assertTrue(obj.run({'target': 'valid'})) @@ -124,6 +141,7 @@ class TestDiscovery(unittest.TestCase): 'false': koji.policy.FalseTest, 'has': koji.policy.HasTest, 'match': koji.policy.MatchTest, + 'adv_match': koji.policy.AdvancedMatchTest, 'none': koji.policy.NoneTest, 'target': koji.policy.TargetTest, 'true': koji.policy.TrueTest, From 626f84736b61a31454c4e21bdb85ffaef0b5cda7 Mon Sep 17 00:00:00 2001 From: Yu Ming Zhu Date: Jul 07 2021 13:39:28 +0000 Subject: [PATCH 2/2] [policy] FindTest for list item matching --- diff --git a/koji/policy.py b/koji/policy.py index 34cf308..d972c95 100644 --- a/koji/policy.py +++ b/koji/policy.py @@ -141,11 +141,11 @@ class MatchTest(BaseSimpleTest): return False -class AdvancedMatchTest(MatchTest): +class AdvancedMatchTest(BaseSimpleTest): """Matches a field in the data against glob patterns The field could be the same as match test, - or a dot-spiited string which indicates key in a (nested) dict + or a dot-splited string which indicates key in a (nested) dict True if any of the expressions match, else False This test can be used as-is, or it can be subclassed to @@ -181,6 +181,32 @@ class AdvancedMatchTest(MatchTest): return False +class FindTest(BaseSimpleTest): + """Matches any item of a list/tuple/set value in the data against glob patterns + + True if any of the expressions matches any item in the list/tuple/set, else False. + If the field doesn't exist or isn't a list/tuple/set, the test returns False + + Syntax: + find field pattern1 [pattern2 ...] + + """ + name = 'find' + field = None + + def run(self, data): + args = self.str.split()[1:] + self.field = args[0] + args = args[1:] + tgt = data.get(self.field) + if tgt and isinstance(tgt, (list, tuple, set)): + for pattern in args: + for i in tgt: + if fnmatch.fnmatch(i, pattern): + return True + return False + + class TargetTest(MatchTest): """Matches target in the data against glob patterns diff --git a/tests/test_lib/test_policy.py b/tests/test_lib/test_policy.py index be2630b..eb7934b 100644 --- a/tests/test_lib/test_policy.py +++ b/tests/test_lib/test_policy.py @@ -83,6 +83,13 @@ class TestBasicTests(unittest.TestCase): } }})) + def test_find_test(self): + obj = koji.policy.FindTest('not_important foo *bar*') + self.assertTrue(obj.run({'foo': ['barrrr', 'any']})) + self.assertFalse(obj.run({'foo': ['nah....']})) + self.assertFalse(obj.run({'foo': 'nah...'})) + self.assertFalse(obj.run({'bar': ['any']})) + def test_target_test(self): obj = koji.policy.TargetTest('target valid') self.assertTrue(obj.run({'target': 'valid'})) @@ -142,6 +149,7 @@ class TestDiscovery(unittest.TestCase): 'has': koji.policy.HasTest, 'match': koji.policy.MatchTest, 'adv_match': koji.policy.AdvancedMatchTest, + 'find': koji.policy.FindTest, 'none': koji.policy.NoneTest, 'target': koji.policy.TargetTest, 'true': koji.policy.TrueTest,