aboutsummaryrefslogtreecommitdiff
path: root/trigger.py
blob: 7d2fdfd66d9e55d6e16abcd139baccb98007c2c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from typing import Dict
from pyparsing import alphanums, alphas, printables, pyparsing_common, pyparsing_common, Word, infix_notation, CaselessKeyword, opAssoc, ParserElement
import logging
import time

import endpoint
import misc

'''
Implementations of Trigger:

MUST implement:
  _evaluate(self, action: str) -> bool
    evaluates the instace for action given by 'action'.
    Provided configuration is stored in self._instances[action]['args']

CAN implement:
  _addInstance(self, action: str)
    Called afer 'action' was added.

SHOULDNT implement:
  evaluate(self, action: str) -> bool
    Only calls _evaluate(), if no check was performed in configured interval,
    otherwise returns cached result
  addInstance(self, action:str, interval=30, **kwargs)
'''
class Trigger:
    NEEDS_CONTEXT = False

    @staticmethod
    def create(classname: str, **kwargs):
        return misc.import_class(classname)(**kwargs)

    def __init__(self):
        self._instances = {}

    def _addInstance(self, action: str):
        pass

    def addInstance(self, action: str, interval: int=30, **kwargs):
        self._instances[action] = {'lastupdate':0,'interval':interval,'last':False,'args':kwargs}
        self._addInstance(action)
        logging.debug(f'Trigger: Action "{action}" registered.')

    def _evaluate(self, action: str) -> bool:
        raise NotImplemented

    def _shouldReevaluate(self, action: str) -> bool:
        return time.time() - self._instances[action]['lastupdate'] > self._instances[action]['interval']

    def evaluate(self, action: str) -> bool:
        if action not in self._instances:
            logging.error(f'Trigger: Action "{action}" was not found. Evaluating to False.')
            return False

        if self._shouldReevaluate(action):
            logging.debug(f'Re-evaluating trigger condition for action "{action}"')
            result =  self._evaluate(action)

            self._instances[action]['last'] = result
            self._instances[action]['lastupdate'] = time.time()
            return result

        return self._instances[action]['last']

'''
```yaml
conditional:
  class: trigger.Conditional
---
- conditional:
  interval: 30
  when:
    - host1.user.bob > 0
```
'''
class ConditionalTrigger(Trigger):
    NEEDS_CONTEXT = True

    def __init__(self, endpoints: Dict[str, endpoint.Endpoint]):
        super().__init__()

        self._endpoints = endpoints
        self._setup_parser()

    def _setup_parser(self):
        ParserElement.enable_packrat()

        boolean = CaselessKeyword('True').setParseAction(lambda x: True) | CaselessKeyword('False').setParseAction(lambda x: False)
        integer = pyparsing_common.integer
        variable = Word(alphanums + '.').setParseAction(self._parseVariable)
        operand = boolean | integer | variable

        self._parser = infix_notation(
                operand,
                [
                    ('not', 1, opAssoc.RIGHT, lambda a: not a[0][1]),
                    ('and', 2, opAssoc.LEFT, lambda a: a[0][0] and a[0][2]),
                    ('or', 2, opAssoc.LEFT, lambda a: a[0][0] or a[0][2]),
                    ('==', 2, opAssoc.LEFT, lambda a: a[0][0] == a[0][2]),
                    ('>', 2, opAssoc.LEFT, lambda a: a[0][0] > a[0][2]),
                    ('>=', 2, opAssoc.LEFT, lambda a: a[0][0] >= a[0][2]),
                    ('<', 2, opAssoc.LEFT, lambda a: a[0][0] < a[0][2]),
                    ('<=', 2, opAssoc.LEFT, lambda a: a[0][0] <= a[0][2]),
                    ('+', 2, opAssoc.LEFT, lambda a: a[0][0] + a[0][2]),
                    ('-', 2, opAssoc.LEFT, lambda a: a[0][0] - a[0][2]),
                    ('*', 2, opAssoc.LEFT, lambda a: a[0][0] * a[0][2]),
                    ('/', 2, opAssoc.LEFT, lambda a: a[0][0] / a[0][2]),
                ]
            )

    def _parseVariable(self, var):
        logging.debug(f'Looking up variable "{var[0]}"')
        endpoint, key = var[0].split('.',1)

        if not endpoint in self._endpoints:
            logging.error(f'Parser: Endpoint "{endpoint}" not found')
            return None

        return self._endpoints[endpoint].getState(key)

    def _evaluate(self, action: str) -> bool:
        return all(self._parser.parse_string(str(s)) for s in self._instances[action]['args']['when'])