示例#1
0
    def load(self, data, *, many=None, partial=None, unknown=None, **kwargs):
        # When being passed in via the query string, we may get the raw JSON string instead of
        # the deserialized dictionary. We need to unpack it ourselves.
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.decoder.JSONDecodeError as exc:
                raise ValidationError({
                    "_schema": [
                        f"Invalid JSON value: '{data}'",
                        str(exc),
                    ],
                })
        elif isinstance(data, QueryExpression):
            return data

        if not self.context or "table" not in self.context:
            raise RuntimeError(f"No table in context for field {self}")

        if not data:
            return NothingExpression()

        try:
            tree_to_expr(data, self.context["table"])
        except ValueError as e:
            raise ValidationError(str(e)) from e
        return super().load(data,
                            many=many,
                            partial=partial,
                            unknown=unknown,
                            **kwargs)
示例#2
0
    def __init__(
            self,
            columns: List[Column],
            filter_expr: QueryExpression = NothingExpression(),
    ):
        """A representation of a livestatus query.

        Args:
            columns:
                A list of `Column` instances, these have to be defined as properties on a
                `Table` class.

            filter_expr:
                A filter-expression. These can be created by comparing `Column` instances to
                something or comparing `LiteralExpression` instances to something.

        """
        self.columns = columns
        self.column_names = [col.query_name for col in columns]
        self.filter_expr = filter_expr
        _tables = {column.table for column in columns}
        if len(_tables) != 1:
            raise ValueError(
                f"Query doesn't specify a single table: {_tables!r}")

        self.table: Type[Table] = _tables.pop()
示例#3
0
    def from_string(
        cls,
        string_query: str,
    ) -> 'Query':
        """Constructs a Query instance from a string based LiveStatus-Query

        Args:
            string_query:
                A LiveStatus query as a string.

        Examples:

            >>> q = Query.from_string('GET services\\n'
            ...                       'Columns: service_service_description\\n'
            ...                       'Filter: service_service_description = \\n')

            >>> query_text = ('''GET services
            ... Columns: host_address host_check_command host_check_type host_custom_variable_names host_custom_variable_values host_downtimes_with_extra_info host_file name host_has_been_checked host_name host_scheduled_downtime_depth host_state service_accept_passive_checks service_acknowledged service_action_url_expanded service_active_checks_enabled service_cache_interval service_cached_at service_check_command service_check_type service_comments_with_extra_info service_custom_variable_names service_custom_variable_values service_custom_variables service_description service_downtimes service_downtimes_with_extra_info service_has_been_checked service_host_name service_icon_image service_in_check_period service_in_notification_period service_in_passive_check_period service_in_service_period service_is_flapping service_last_check service_last_state_change service_modified_attributes_list service_notes_url_expanded service_notifications_enabled service_perf_data service_plugin_output service_pnpgraph_present service_scheduled_downtime_depth service_service_description service_staleness service_state
            ... Filter: service_state = 0
            ... Filter: service_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... Filter: service_has_been_checked = 1
            ... Filter: service_scheduled_downtime_depth = 0
            ... Filter: host_scheduled_downtime_depth = 0
            ... And: 2
            ... Filter: service_acknowledged = 0
            ... Filter: host_state = 1
            ... Filter: host_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... Filter: host_state = 2
            ... Filter: host_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... ''')
            >>> q = Query.from_string(query_text)

            We can faithfully recreate this query as a dict-representation.

            >>> q.dict_repr()
            {'op': 'and', 'expr': [\
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.state', 'right': '0'}, \
{'op': '=', 'left': 'services.has_been_checked', 'right': '1'}\
]}}, \
{'op': '=', 'left': 'services.has_been_checked', 'right': '1'}, \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.scheduled_downtime_depth', 'right': '0'}, \
{'op': '=', 'left': 'services.host_scheduled_downtime_depth', 'right': '0'}\
]}, \
{'op': '=', 'left': 'services.acknowledged', 'right': '0'}, \
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.host_state', 'right': '1'}, \
{'op': '=', 'left': 'services.host_has_been_checked', 'right': '1'}\
]}}, \
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.host_state', 'right': '2'}, \
{'op': '=', 'left': 'services.host_has_been_checked', 'right': '1'}\
]}}\
]}

            >>> q.columns
            [Column(services.host_address: string), Column(services.host_check_command: string), Column(services.host_check_type: int), Column(services.host_custom_variable_names: list), Column(services.host_custom_variable_values: list), Column(services.host_downtimes_with_extra_info: list), Column(services.host_has_been_checked: int), Column(services.host_name: string), Column(services.host_scheduled_downtime_depth: int), Column(services.host_state: int), Column(services.accept_passive_checks: int), Column(services.acknowledged: int), Column(services.action_url_expanded: string), Column(services.active_checks_enabled: int), Column(services.cache_interval: int), Column(services.cached_at: time), Column(services.check_command: string), Column(services.check_type: int), Column(services.comments_with_extra_info: list), Column(services.custom_variable_names: list), Column(services.custom_variable_values: list), Column(services.custom_variables: dict), Column(services.description: string), Column(services.downtimes: list), Column(services.downtimes_with_extra_info: list), Column(services.has_been_checked: int), Column(services.host_name: string), Column(services.icon_image: string), Column(services.in_check_period: int), Column(services.in_notification_period: int), Column(services.in_passive_check_period: int), Column(services.in_service_period: int), Column(services.is_flapping: int), Column(services.last_check: time), Column(services.last_state_change: time), Column(services.modified_attributes_list: list), Column(services.notes_url_expanded: string), Column(services.notifications_enabled: int), Column(services.perf_data: string), Column(services.plugin_output: string), Column(services.pnpgraph_present: int), Column(services.scheduled_downtime_depth: int), Column(services.description: string), Column(services.staleness: float), Column(services.state: int)]

            >>> q = Query.from_string('GET hosts\\n'
            ...                       'Columns: name service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table 'hosts': Could not decode line 'Filter: service_description = '

            All unknown columns are ignored, as there are many places in Checkmk where queries
            specify wrong or unnecessary columns. Livestatus would normally ignore them.

            >>> _ = Query.from_string('GET hosts\\n'
            ...                       'Columns: service_service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table 'hosts': Could not decode line 'Filter: service_description = '

            >>> _ = Query.from_string('GET foobazbar\\n'
            ...                       'Columns: name service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table foobazbar was not defined in the tables module.


            >>> q = Query.from_string('GET hosts\\n'
            ...                       'Columns: name\\n'
            ...                       'Filter: name = heute\\n'
            ...                       'Filter: alias = heute\\n'
            ...                       'Or: 2')


            >>> q.table.__name__
            'Hosts'

            >>> q.columns
            [Column(hosts.name: string)]

            >>> q.filter_expr
            Or(Filter(name = heute), Filter(alias = heute))

            >>> print(q)
            GET hosts
            Columns: name
            Filter: name = heute
            Filter: alias = heute
            Or: 2

            So in essence this says that round trips work

                >>> assert str(q) == str(Query.from_string(str(q)))

        Returns:
            A Query instance.

        Raises:
            A ValueError if no Query() instance could be created.

        """
        lines = string_query.split("\n")
        for line in lines:
            if line.startswith('GET '):
                parts = line.split()
                if len(parts) < 2:
                    raise ValueError(f"No table found in line: {line!r}")

                table_name = parts[1]
                try:
                    table_class: Type[Table] = getattr(tables,
                                                       table_name.title())
                except AttributeError:
                    raise ValueError(
                        f"Table {table_name} was not defined in the tables module."
                    )
                break
        else:
            raise ValueError("No table found")

        for line in lines:
            if line.startswith('Columns: '):
                column_names = line.split(": ", 1)[1].lstrip().split()
                columns: List[Column] = []
                for col in column_names:
                    try:
                        columns.append(_get_column(table_class, col))
                    except AttributeError:
                        pass
                break
        else:
            raise ValueError("No columns found")

        filters: List[QueryExpression] = []
        for line in lines:
            if line.startswith('Filter: '):
                try:
                    filters.append(_parse_line(table_class, line))
                except AttributeError:
                    raise ValueError(
                        f"Table {table_name!r}: Could not decode line {line!r}"
                    )
            elif line.startswith('Or: ') or line.startswith("And: "):
                op, _count = line.split(": ")
                count = int(_count)
                # I'm sorry. :)
                # We take the last `count` filters and pass them into the BooleanExpression
                try:
                    expr = {
                        'or': Or,
                        'and': And
                    }[op.lower()](*filters[-count:])
                except ValueError:
                    raise ValueError(f"Could not parse {op} for {filters!r}")
                filters = filters[:-count]
                filters.append(expr)
            elif line.startswith('Negate:') or line.startswith('Not:'):
                filters[-1] = Not(filters[-1])

        if len(filters) > 1:
            filters = [And(*filters)]

        return cls(
            columns=columns,
            filter_expr=filters[0] if filters else NothingExpression(),
        )